diff --git a/CODEOWNERS b/CODEOWNERS index 8a82b49aa7d0a..e1fe2044e900b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -308,7 +308,7 @@ /bundles/org.openhab.binding.smhi/ @pacive /bundles/org.openhab.binding.smsmodem/ @dalgwen /bundles/org.openhab.binding.sncf/ @clinique -/bundles/org.openhab.binding.snmp/ @openhab/add-ons-maintainers +/bundles/org.openhab.binding.snmp/ @J-N-K /bundles/org.openhab.binding.solaredge/ @alexf2015 /bundles/org.openhab.binding.solarlog/ @johannrichard /bundles/org.openhab.binding.solarmax/ @jamietownsend diff --git a/bundles/org.openhab.binding.snmp/README.md b/bundles/org.openhab.binding.snmp/README.md index a795fc7bb97e0..e2cd61acb4a69 100644 --- a/bundles/org.openhab.binding.snmp/README.md +++ b/bundles/org.openhab.binding.snmp/README.md @@ -2,12 +2,16 @@ This binding integrates the Simple Network Management Protocol (SNMP). SNMP can be used to monitor or control a large variety of network equipment, e.g. routers, switches, NAS-systems. -Currently protocol version 1 and 2c are supported. +Currently, protocol version 1 and 2c are supported. ## Supported Things -Only one thing is supported: `target`. -It represents a single network device. +There are two supported things: + + - `target` for SNMP v1/v2c agents + - `target3` for SNMP v3 agents + +Both represent a single network device. Things can be extended with `number`, `string` and `switch` channels. ## Binding Configuration @@ -17,7 +21,7 @@ In this case the `port` parameter defaults to `0`. For receiving traps a port for receiving traps needs to be configured. The standard port for receiving traps is 162, however binding to ports lower than 1024 is only allowed with privileged right on most *nix systems. -Therefore it is recommended to bind to a port higher than 1024 (e.g. 8162). +Therefore, it is recommended to bind to a port higher than 1024 (e.g. 8162). In case the trap sending equipment does not allow to change the destination port (e.g. Mikrotik routers), it is necessary to forward the received packets to the new port. This can be done either by software like _snmptrapd_ or by adding a firewall rule to your system, e.g. by executing @@ -40,19 +44,11 @@ port=8162 ## Thing Configuration -The `target` thing has one mandatory parameter: `hostname`. -It can be set as FQDN or IP address. - -Optional configuration parameters are `community`, `version` and `refresh`. - -The SNMP community can be set with the `community` parameter. -It defaults to `public`. +### Common parameters for all thing-types -Currently two protocol versions are supported. -The protocol version can be set with the `protocol` parameter. -The allowed values are `v1` or `V1`for v1 and `v2c` or `V2C` for v2c. -The default is `v1`. +The `hostname` is mandatory and can be set as FQDN or IP address. +An optional configuration parameter is `refresh`. By using the `refresh` parameter the time between two subsequent GET requests to the target can be set. The default is `60` for 60s. @@ -67,6 +63,44 @@ A single request times out after `timeout` ms. After `retries` timeouts the refresh operation is considered to be fails and the status of the thing set accordingly. The default values are `timeout=1500` and `retries=2`. +### `target` + +The `target` thing has two optional configuration parameters: `community` and `version`. + +The SNMP community for SNMP version 2c can be set with the `community` parameter. +It defaults to `public`. + +Currently two protocol versions are supported. +The protocol version can be set with the `protocol` parameter. +The allowed values are `v1` or `V1` for v1 and `v2c` or `V2C` for v2c. +The default is `v1`. + +### `target3` + +The `target3` thing has additional mandatory parameters: `engineId` and `user`. + +The `engineId` must be given in hexadecimal notation (case-insensitive) without separators (e.g. `80000009035c710dbcd9e6`). +The allowed length is 11 to 32 bytes (22 to 64 hex characters). +If you encounter problems, please check if your agent prefixes the set engine id (e.g. Mikrotik uses `80003a8c04` and appends the set value to that). + +The `user` parameter is named "securityName" or "userName" in most agents. + +Optional configuration parameters are: `securityModel`, `authProtocol`, `authPassphrase`, `privProtocol` and `privPassphrase`. + +The `securityModel` can be set to + +- `NO_AUTH_NO_PRIV` (default) - no encryption on authentication data, no encryption on transmitted data +- `AUTH_NO_PRIV` - encryption on authentication data, no encryption on transmitted data +- `AUTH_PRIV` - encryption on authentication data, encryption on transmitted data + +Depending on the `securityModel` some of the other parameters are also mandatory. + +If authentication encryption is required, at least `authPassphrase` needs to be set, while `authProtocol` has a default of `MD5`. +Other possible values for `authProtocol` are `SHA`, `HMAC128SHA224`, `HMAC192SHA256`, `HMAC256SHA384` and `HMAC384SHA512`. + +If encryption of transmitted data (privacy encryption) is required, at least `privPassphrase` needs to be set, while `privProtocol` defaults to `DES`. +Other possible values for `privProtocol` are `AES128`, `AES192` and `AES256`. + ## Channels The `target` thing has no fixed channels. @@ -87,7 +121,7 @@ Using`TRAP` channels requires configuring the receiving port (see "Binding confi The `datatype` parameter is needed in some special cases where data is written to the target. The default `datatype` for `number` channels is `UINT32`, representing an unsigned integer with 32 bit length. Alternatively `INT32` (signed integer with 32 bit length), `COUNTER64` (unsigned integer with 64 bit length) or `FLOAT` (floating point number) can be set. -Floating point numbers have to be supplied (and will be send) as strings. +Floating point numbers have to be supplied (and will be sent) as strings. For `string` channels the default `datatype` is `STRING` (i.e. the item's will be sent as a string). 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). @@ -99,11 +133,16 @@ In `READ`, `READ_WRITE` or `TRAP` mode they change to either `ON` or `OFF` on th The parameters used for defining the values are `onvalue` and `offvalue`. The `datatype` parameter is used to convert the configuration strings to the needed values. -| type | item | description | -| ------ | ------ | ------------------------------ | -| number | Number | a channel with a numeric value | -| string | String | a channel with a string value | -| switch | Switch | a channel that has two states | +`number`-type channels have a `unit` parameter. +The unit is added to the received value before it is passed to the channel. +For commands (i.e. sending), the value is first converted to the configured unit. + +| type | item | description | +|----------|--------|---------------------------------| +| number | Number | a channel with a numeric value | +| string | String | a channel with a string value | +| switch | Switch | a channel that has two states | + ### SNMP Exception (Error) Handling @@ -121,10 +160,10 @@ Valid values are all valid values for that channel (i.e. `ON`/`OFF` for a switch demo.things: -```java +``` 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", unit="B" ] + Type number : inBytes [ oid=".1.3.6.1.2.1.31.1.1.1.6.2", mode="READ" ] 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" ] @@ -137,7 +176,6 @@ demo.items: ```java 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" } diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpBindingConstants.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpBindingConstants.java index 399a6ef6b1f4b..74ba891d64979 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpBindingConstants.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpBindingConstants.java @@ -29,6 +29,7 @@ public class SnmpBindingConstants { // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_TARGET = new ThingTypeUID(BINDING_ID, "target"); + public static final ThingTypeUID THING_TYPE_TARGET3 = new ThingTypeUID(BINDING_ID, "target3"); public static final ChannelTypeUID CHANNEL_TYPE_UID_NUMBER = new ChannelTypeUID(BINDING_ID, "number"); public static final ChannelTypeUID CHANNEL_TYPE_UID_STRING = new ChannelTypeUID(BINDING_ID, "string"); diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpHandlerFactory.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpHandlerFactory.java index b28895d58855b..6ee2a6047680b 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpHandlerFactory.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpHandlerFactory.java @@ -13,8 +13,8 @@ package org.openhab.binding.snmp.internal; import static org.openhab.binding.snmp.internal.SnmpBindingConstants.THING_TYPE_TARGET; +import static org.openhab.binding.snmp.internal.SnmpBindingConstants.THING_TYPE_TARGET3; -import java.util.Collections; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -37,7 +37,7 @@ @NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.snmp") public class SnmpHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_TARGET); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_TARGET, THING_TYPE_TARGET3); private final SnmpService snmpService; @@ -54,7 +54,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (THING_TYPE_TARGET.equals(thingTypeUID)) { + if (THING_TYPE_TARGET.equals(thingTypeUID) || THING_TYPE_TARGET3.equals(thingTypeUID)) { return new SnmpTargetHandler(thing, snmpService); } return null; diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpService.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpService.java index 2f554445ec18c..17e87d18cbb3a 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpService.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpService.java @@ -16,6 +16,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.snmp.internal.types.SnmpAuthProtocol; +import org.openhab.binding.snmp.internal.types.SnmpPrivProtocol; import org.snmp4j.CommandResponder; import org.snmp4j.PDU; import org.snmp4j.Target; @@ -31,9 +33,12 @@ @NonNullByDefault public interface SnmpService { - public void addCommandResponder(CommandResponder listener); + void addCommandResponder(CommandResponder listener); - public void removeCommandResponder(CommandResponder listener); + void removeCommandResponder(CommandResponder listener); - public void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener) throws IOException; + void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener) throws IOException; + + void addUser(String userName, SnmpAuthProtocol snmpAuthProtocol, @Nullable String authPassphrase, + SnmpPrivProtocol snmpPrivProtocol, @Nullable String privPassphrase, byte[] engineId); } diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpServiceImpl.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpServiceImpl.java index 2aa9b55805e21..4f655a6140f05 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpServiceImpl.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpServiceImpl.java @@ -14,12 +14,16 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.snmp.internal.config.SnmpServiceConfiguration; +import org.openhab.binding.snmp.internal.types.SnmpAuthProtocol; +import org.openhab.binding.snmp.internal.types.SnmpPrivProtocol; import org.openhab.core.config.core.Configuration; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -32,8 +36,13 @@ import org.snmp4j.Snmp; import org.snmp4j.Target; import org.snmp4j.event.ResponseListener; +import org.snmp4j.mp.MPv3; import org.snmp4j.security.Priv3DES; +import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; +import org.snmp4j.security.USM; +import org.snmp4j.security.UsmUser; +import org.snmp4j.smi.OctetString; import org.snmp4j.smi.UdpAddress; import org.snmp4j.transport.DefaultUdpTransportMapping; @@ -53,10 +62,18 @@ public class SnmpServiceImpl implements SnmpService { private @Nullable Snmp snmp; private @Nullable DefaultUdpTransportMapping transport; - private List listeners = new ArrayList<>(); + private final List listeners = new ArrayList<>(); + private final Set userEntries = new HashSet<>(); @Activate public SnmpServiceImpl(Map config) { + SecurityProtocols.getInstance().addDefaultProtocols(); + SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES()); + + OctetString localEngineId = new OctetString(MPv3.createLocalEngineID()); + USM usm = new USM(SecurityProtocols.getInstance(), localEngineId, 0); + SecurityModels.getInstance().addSecurityModel(usm); + modified(config); } @@ -78,9 +95,12 @@ protected void modified(Map config) { SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES()); final Snmp snmp = new Snmp(transport); - listeners.forEach(listener -> snmp.addCommandResponder(listener)); + listeners.forEach(snmp::addCommandResponder); snmp.listen(); + // re-add user entries + userEntries.forEach(u -> addUser(snmp, u)); + this.snmp = snmp; this.transport = transport; @@ -90,6 +110,7 @@ protected void modified(Map config) { } } + @SuppressWarnings("unused") @Deactivate public void deactivate() { try { @@ -141,4 +162,37 @@ public void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseLi logger.warn("SNMP service not initialized, can't send {} to {}", pdu, target); } } + + @Override + public void addUser(String userName, SnmpAuthProtocol snmpAuthProtocol, @Nullable String authPassphrase, + SnmpPrivProtocol snmpPrivProtocol, @Nullable String privPassphrase, byte[] engineId) { + UsmUser usmUser = new UsmUser(new OctetString(userName), snmpAuthProtocol.getOid(), + authPassphrase != null ? new OctetString(authPassphrase) : null, snmpPrivProtocol.getOid(), + privPassphrase != null ? new OctetString(privPassphrase) : null); + OctetString securityNameOctets = new OctetString(userName); + + UserEntry userEntry = new UserEntry(securityNameOctets, new OctetString(engineId), usmUser); + userEntries.add(userEntry); + + Snmp snmp = this.snmp; + if (snmp != null) { + addUser(snmp, userEntry); + } + } + + private static void addUser(Snmp snmp, UserEntry userEntry) { + snmp.getUSM().addUser(userEntry.securityName, userEntry.engineId, userEntry.user); + } + + private static class UserEntry { + public OctetString securityName; + public OctetString engineId; + public UsmUser user; + + public UserEntry(OctetString securityName, OctetString engineId, UsmUser user) { + this.securityName = securityName; + this.engineId = engineId; + this.user = user; + } + } } diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpTargetHandler.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpTargetHandler.java index 647ca8dfdda9c..6709a817a3144 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpTargetHandler.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpTargetHandler.java @@ -15,7 +15,6 @@ 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; @@ -28,13 +27,16 @@ 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; import org.openhab.binding.snmp.internal.config.SnmpInternalChannelConfiguration; import org.openhab.binding.snmp.internal.config.SnmpTargetConfiguration; +import org.openhab.binding.snmp.internal.types.SnmpChannelMode; +import org.openhab.binding.snmp.internal.types.SnmpDatatype; +import org.openhab.binding.snmp.internal.types.SnmpProtocolVersion; +import org.openhab.binding.snmp.internal.types.SnmpSecurityModel; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; @@ -51,6 +53,7 @@ import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; import org.openhab.core.types.util.UnitUtils; +import org.openhab.core.util.HexUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snmp4j.AbstractTarget; @@ -59,7 +62,9 @@ import org.snmp4j.CommunityTarget; import org.snmp4j.PDU; import org.snmp4j.PDUv1; +import org.snmp4j.ScopedPDU; import org.snmp4j.Snmp; +import org.snmp4j.UserTarget; import org.snmp4j.event.ResponseEvent; import org.snmp4j.event.ResponseListener; import org.snmp4j.mp.SnmpConstants; @@ -68,6 +73,7 @@ import org.snmp4j.smi.IpAddress; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.Opaque; import org.snmp4j.smi.UdpAddress; import org.snmp4j.smi.UnsignedInteger32; import org.snmp4j.smi.Variable; @@ -114,11 +120,13 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof RefreshType) { SnmpInternalChannelConfiguration channel = readChannelSet.stream() .filter(c -> channelUID.equals(c.channelUID)).findFirst() - .orElseThrow(() -> new IllegalArgumentException("no writable channel found")); - PDU pdu = new PDU(PDU.GET, Collections.singletonList(new VariableBinding(channel.oid))); + .orElseThrow(() -> new IllegalArgumentException("no readable channel found")); + PDU pdu = getPDU(); + pdu.setType(PDU.GET); + pdu.add(new VariableBinding(channel.oid)); snmpService.send(pdu, target, null, this); - } else if (command instanceof DecimalType || command instanceof StringType - || command instanceof OnOffType) { + } else if (command instanceof DecimalType || command instanceof QuantityType + || command instanceof StringType || command instanceof OnOffType) { SnmpInternalChannelConfiguration channel = writeChannelSet.stream() .filter(config -> channelUID.equals(config.channelUID)).findFirst() .orElseThrow(() -> new IllegalArgumentException("no writable channel found")); @@ -130,9 +138,25 @@ public void handleCommand(ChannelUID channelUID, Command command) { return; } } else { - variable = convertDatatype(command, channel.datatype); + Command rawValue = command; + if (command instanceof QuantityType) { + Unit channelUnit = channel.unit; + if (channelUnit == null) { + rawValue = new DecimalType(((QuantityType) command).toBigDecimal()); + } else { + QuantityType convertedValue = ((QuantityType) command).toUnit(channelUnit); + if (convertedValue == null) { + logger.warn("Cannot convert '{}' to configured unit '{}'", command, channelUnit); + return; + } + rawValue = new DecimalType(convertedValue.toBigDecimal()); + } + } + variable = convertDatatype(rawValue, channel.datatype); } - PDU pdu = new PDU(PDU.SET, Collections.singletonList(new VariableBinding(channel.oid, variable))); + PDU pdu = getPDU(); + pdu.setType(PDU.SET); + pdu.add(new VariableBinding(channel.oid, variable)); snmpService.send(pdu, target, null, this); } } catch (IllegalArgumentException e) { @@ -148,23 +172,71 @@ public void initialize() { generateChannelConfigs(); - if (config.protocol.toInteger() == SnmpConstants.version1 - || config.protocol.toInteger() == SnmpConstants.version2c) { - CommunityTarget target = new CommunityTarget(); - target.setCommunity(new OctetString(config.community)); + if (thing.getThingTypeUID().equals(THING_TYPE_TARGET3)) { + // override default for target3 things + config.protocol = SnmpProtocolVersion.v3; + } + + try { + if (config.protocol.toInteger() == SnmpConstants.version1 + || config.protocol.toInteger() == SnmpConstants.version2c) { + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString(config.community)); + this.target = target; + } else if (config.protocol.toInteger() == SnmpConstants.version3) { + String userName = config.user; + if (userName == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "user not set"); + return; + } + String engineIdHexString = config.engineId; + if (engineIdHexString == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "engineId not set"); + return; + } + String authPassphrase = config.authPassphrase; + if ((config.securityModel == SnmpSecurityModel.AUTH_PRIV + || config.securityModel == SnmpSecurityModel.AUTH_NO_PRIV) + && (authPassphrase == null || authPassphrase.isEmpty())) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Authentication passphrase not configured"); + return; + } + String privPassphrase = config.privPassphrase; + if (config.securityModel == SnmpSecurityModel.AUTH_PRIV + && (privPassphrase == null || privPassphrase.isEmpty())) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Privacy passphrase not configured"); + return; + } + byte[] engineId = HexUtils.hexToBytes(engineIdHexString); + snmpService.addUser(userName, config.authProtocol, authPassphrase, config.privProtocol, privPassphrase, + engineId); + UserTarget target = new UserTarget(); + target.setAuthoritativeEngineID(engineId); + target.setSecurityName(new OctetString(config.user)); + target.setSecurityLevel(config.securityModel.getSecurityLevel()); + this.target = target; + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "SNMP version not supported"); + return; + } + + snmpService.addCommandResponder(this); + target.setRetries(config.retries); target.setTimeout(config.timeout); target.setVersion(config.protocol.toInteger()); target.setAddress(null); - this.target = target; - snmpService.addCommandResponder(this); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "SNMP version not supported"); + + timeoutCounter = 0; + } catch (IllegalArgumentException e) { + // some methods of SNMP4J throw an unchecked IllegalArgumentException if they receive invalid values + String message = "Exception during initialization: " + e.getMessage(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message); return; } - timeoutCounter = 0; - updateStatus(ThingStatus.UNKNOWN); refresh = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refresh, TimeUnit.SECONDS); } @@ -230,9 +302,8 @@ public void processPdu(@Nullable CommandResponderEvent event) { final String address = ((UdpAddress) event.getPeerAddress()).getInetAddress().getHostAddress(); final String community = new String(event.getSecurityName()); - if ((pdu.getType() == PDU.V1TRAP) && config.community.equals(community) && (pdu instanceof PDUv1)) { + if ((pdu.getType() == PDU.V1TRAP) && config.community.equals(community) && (pdu instanceof PDUv1 pduv1)) { logger.trace("{} received trap is PDUv1.", thing.getUID()); - PDUv1 pduv1 = (PDUv1) pdu; OID oidEnterprise = pduv1.getEnterprise(); int trapValue = pduv1.getGenericTrap(); if (trapValue == PDUv1.ENTERPRISE_SPECIFIC) { @@ -262,8 +333,8 @@ public void processPdu(@Nullable CommandResponderEvent event) { SnmpDatatype datatype = config.datatype; // maybe null, override later Variable onValue = null; Variable offValue = null; - State exceptionValue = UnDefType.UNDEF; Unit unit = null; + State exceptionValue = UnDefType.UNDEF; if (CHANNEL_TYPE_UID_NUMBER.equals(channel.getChannelTypeUID())) { if (datatype == null) { @@ -275,15 +346,11 @@ public void processPdu(@Nullable CommandResponderEvent event) { 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()); - } + String configUnit = config.unit; + if (configUnit != null) { + unit = UnitUtils.parseUnit(configUnit); + if (unit == null) { + logger.warn("Failed to parse unit from '{}'for channel '{}'", unit, channel.getUID()); } } } else if (CHANNEL_TYPE_UID_STRING.equals(channel.getChannelTypeUID())) { @@ -323,13 +390,12 @@ public void processPdu(@Nullable CommandResponderEvent event) { return null; } return new SnmpInternalChannelConfiguration(channel.getUID(), new OID(oid), config.mode, datatype, onValue, - offValue, exceptionValue, config.doNotLogException, unit); + offValue, exceptionValue, unit, config.doNotLogException); } private void generateChannelConfigs() { - Set channelConfigs = Collections - .unmodifiableSet(thing.getChannels().stream().map(channel -> getChannelConfigFromChannel(channel)) - .filter(Objects::nonNull).collect(Collectors.toSet())); + Set channelConfigs = Collections.unmodifiableSet(thing.getChannels().stream() + .map(this::getChannelConfigFromChannel).filter(Objects::nonNull).collect(Collectors.toSet())); this.readChannelSet = channelConfigs.stream() .filter(c -> c.mode == SnmpChannelMode.READ || c.mode == SnmpChannelMode.READ_WRITE) .collect(Collectors.toSet()); @@ -359,17 +425,37 @@ private void updateChannels(OID oid, Variable value, Set unit = channelConfig.unit; if (channelConfig.datatype == SnmpDatatype.FLOAT) { - numericState = new BigDecimal(value.toString()); - } else { - numericState = BigDecimal.valueOf(value.toLong()); - } - if (unit != null) { - state = new QuantityType<>(numericState, unit); + if (value instanceof Opaque opaque) { + byte[] octets = opaque.toByteArray(); + if (octets.length < 3) { + // two bytes identifier and one byte length should always be present + throw new UnsupportedOperationException("Not enough octets"); + } + if (octets.length != (3 + octets[2])) { + // octet 3 contains the lengths of the value + throw new UnsupportedOperationException("Not enough octets"); + } + if (octets[0] == (byte) 0x9f && octets[1] == 0x78 && octets[2] == 0x04) { + // floating point value + Unit channelUnit = channelConfig.unit; + float floatValue = Float.intBitsToFloat( + octets[3] << 24 | octets[4] << 16 | octets[5] << 8 | octets[6]); + state = channelUnit == null ? new DecimalType(floatValue) + : new QuantityType<>(floatValue, channelUnit); + + } else { + throw new UnsupportedOperationException("Unknown opaque datatype" + value); + } + } else { + Unit channelUnit = channelConfig.unit; + state = channelUnit == null ? new DecimalType(value.toString()) + : new QuantityType<>(value + channelUnit.getSymbol()); + } } else { - state = new DecimalType(numericState); + Unit channelUnit = channelConfig.unit; + state = channelUnit == null ? new DecimalType(value.toLong()) + : new QuantityType<>(value.toLong(), channelUnit); } } catch (UnsupportedOperationException e) { logger.warn("could not convert {} to number for channel {}", value, channelUID); @@ -404,36 +490,35 @@ private void updateChannels(OID oid, Variable value, Set { if (command instanceof DecimalType) { return new Integer32(((DecimalType) command).intValue()); } else if (command instanceof StringType) { return new Integer32((new DecimalType(((StringType) command).toString())).intValue()); } - break; - case UINT32: + } + case UINT32 -> { if (command instanceof DecimalType) { return new UnsignedInteger32(((DecimalType) command).intValue()); } else if (command instanceof StringType) { return new UnsignedInteger32((new DecimalType(((StringType) command).toString())).intValue()); } - break; - case COUNTER64: + } + case COUNTER64 -> { if (command instanceof DecimalType) { return new Counter64(((DecimalType) command).longValue()); } else if (command instanceof StringType) { return new Counter64((new DecimalType(((StringType) command).toString())).longValue()); } - break; - case FLOAT: - case STRING: + } + case FLOAT, STRING -> { if (command instanceof DecimalType) { return new OctetString(((DecimalType) command).toString()); } else if (command instanceof StringType) { return new OctetString(((StringType) command).toString()); } - break; - case HEXSTRING: + } + case HEXSTRING -> { if (command instanceof StringType) { String commandString = ((StringType) command).toString().toLowerCase(); Matcher commandMatcher = HEXSTRING_VALIDITY.matcher(commandString); @@ -442,13 +527,14 @@ private Variable convertDatatype(Command command, SnmpDatatype datatype) { return OctetString.fromHexStringPairs(commandString); } } - break; - case IPADDRESS: + } + case IPADDRESS -> { if (command instanceof StringType) { return new IpAddress(((StringType) command).toString()); } - break; - default: + } + default -> { + } } throw new IllegalArgumentException("illegal conversion of " + command + " to " + datatype); } @@ -472,8 +558,9 @@ private void refresh() { return; } } - PDU pdu = new PDU(PDU.GET, - readChannelSet.stream().map(c -> new VariableBinding(c.oid)).collect(Collectors.toList())); + PDU pdu = getPDU(); + pdu.setType(PDU.GET); + readChannelSet.stream().map(c -> new VariableBinding(c.oid)).forEach(pdu::add); if (!pdu.getVariableBindings().isEmpty()) { try { snmpService.send(pdu, target, null, this); @@ -482,4 +569,12 @@ private void refresh() { } } } + + private PDU getPDU() { + if (config.protocol == SnmpProtocolVersion.v3 || config.protocol == SnmpProtocolVersion.V3) { + return new ScopedPDU(); + } else { + return new PDU(); + } + } } diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpChannelConfiguration.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpChannelConfiguration.java index 19c4499939128..5212936a1b304 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpChannelConfiguration.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpChannelConfiguration.java @@ -14,8 +14,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.snmp.internal.SnmpChannelMode; -import org.openhab.binding.snmp.internal.SnmpDatatype; +import org.openhab.binding.snmp.internal.types.SnmpChannelMode; +import org.openhab.binding.snmp.internal.types.SnmpDatatype; /** * The {@link SnmpChannelConfiguration} class contains fields mapping channel configuration parameters. @@ -27,12 +27,11 @@ public class SnmpChannelConfiguration { public @Nullable String oid; public SnmpChannelMode mode = SnmpChannelMode.READ; public @Nullable SnmpDatatype datatype; + public @Nullable String unit; public @Nullable String onvalue; public @Nullable String offvalue; public @Nullable String exceptionValue; public boolean doNotLogException = false; - - public @Nullable String unit; } diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpInternalChannelConfiguration.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpInternalChannelConfiguration.java index 1bb61bccc36fa..16440d6b8b866 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpInternalChannelConfiguration.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpInternalChannelConfiguration.java @@ -16,8 +16,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.snmp.internal.SnmpChannelMode; -import org.openhab.binding.snmp.internal.SnmpDatatype; +import org.openhab.binding.snmp.internal.types.SnmpChannelMode; +import org.openhab.binding.snmp.internal.types.SnmpDatatype; import org.openhab.core.thing.ChannelUID; import org.openhab.core.types.State; import org.snmp4j.smi.OID; @@ -39,12 +39,12 @@ public class SnmpInternalChannelConfiguration { public final @Nullable Variable onValue; public final @Nullable Variable offValue; public final State exceptionValue; - public final boolean doNotLogException; public final @Nullable Unit unit; + public final boolean doNotLogException; public SnmpInternalChannelConfiguration(ChannelUID channelUID, OID oid, SnmpChannelMode mode, SnmpDatatype datatype, - @Nullable Variable onValue, @Nullable Variable offValue, State exceptionValue, boolean doNotLogException, - @Nullable Unit unit) { + @Nullable Variable onValue, @Nullable Variable offValue, State exceptionValue, @Nullable Unit unit, + boolean doNotLogException) { this.channelUID = channelUID; this.oid = oid; this.mode = mode; @@ -52,7 +52,7 @@ public SnmpInternalChannelConfiguration(ChannelUID channelUID, OID oid, SnmpChan this.onValue = onValue; this.offValue = offValue; this.exceptionValue = exceptionValue; - this.doNotLogException = doNotLogException; this.unit = unit; + this.doNotLogException = doNotLogException; } } diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpTargetConfiguration.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpTargetConfiguration.java index 832bbaf486f21..41aa8569810dd 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpTargetConfiguration.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpTargetConfiguration.java @@ -14,7 +14,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.snmp.internal.SnmpProtocolVersion; +import org.openhab.binding.snmp.internal.types.SnmpAuthProtocol; +import org.openhab.binding.snmp.internal.types.SnmpPrivProtocol; +import org.openhab.binding.snmp.internal.types.SnmpProtocolVersion; +import org.openhab.binding.snmp.internal.types.SnmpSecurityModel; /** * The {@link SnmpTargetConfiguration} class contains fields mapping thing configuration parameters. @@ -23,11 +26,24 @@ */ @NonNullByDefault public class SnmpTargetConfiguration { + // common public @Nullable String hostname; public int port = 161; - public String community = "public"; - public int refresh = 60; public SnmpProtocolVersion protocol = SnmpProtocolVersion.v1; + + public int refresh = 60; public int timeout = 1500; public int retries = 2; + + // v1/v2c only + public String community = "public"; + + // v3 only + public SnmpSecurityModel securityModel = SnmpSecurityModel.NO_AUTH_NO_PRIV; + public @Nullable String user; + public @Nullable String engineId; + public SnmpAuthProtocol authProtocol = SnmpAuthProtocol.MD5; + public @Nullable String authPassphrase; + public SnmpPrivProtocol privProtocol = SnmpPrivProtocol.DES; + public @Nullable String privPassphrase; } diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpAuthProtocol.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpAuthProtocol.java new file mode 100644 index 0000000000000..917e5ef70d9d2 --- /dev/null +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpAuthProtocol.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.snmp.internal.types; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.snmp4j.security.AuthHMAC128SHA224; +import org.snmp4j.security.AuthHMAC192SHA256; +import org.snmp4j.security.AuthHMAC256SHA384; +import org.snmp4j.security.AuthHMAC384SHA512; +import org.snmp4j.security.AuthMD5; +import org.snmp4j.security.AuthSHA; +import org.snmp4j.smi.OID; + +/** + * The {@link SnmpAuthProtocol} enum defines the possible authentication protocols for v3 + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public enum SnmpAuthProtocol { + MD5(AuthMD5.ID), + SHA(AuthSHA.ID), + HMAC128SHA224(AuthHMAC128SHA224.ID), + HMAC192SHA256(AuthHMAC192SHA256.ID), + HMAC256SHA384(AuthHMAC256SHA384.ID), + HMAC384SHA512(AuthHMAC384SHA512.ID); + + private final OID oid; + + SnmpAuthProtocol(OID oid) { + this.oid = oid; + } + + /** + * get the OID for this authentication protocol + * + * @return the corresponding OID + */ + public OID getOid() { + return oid; + } +} diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpChannelMode.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpChannelMode.java similarity index 83% rename from bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpChannelMode.java rename to bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpChannelMode.java index 82a21589f2778..c0273b4152d88 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpChannelMode.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpChannelMode.java @@ -10,14 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.snmp.internal; +package org.openhab.binding.snmp.internal.types; + +import org.eclipse.jdt.annotation.NonNullByDefault; /** * The {@link SnmpChannelMode} enum defines the mode of SNMP channels * * @author Jan N. Klug - Initial contribution */ - +@NonNullByDefault public enum SnmpChannelMode { READ, WRITE, diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpDatatype.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpDatatype.java similarity index 84% rename from bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpDatatype.java rename to bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpDatatype.java index 7101407c7ee52..aa953c68333e8 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpDatatype.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpDatatype.java @@ -10,14 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.snmp.internal; +package org.openhab.binding.snmp.internal.types; + +import org.eclipse.jdt.annotation.NonNullByDefault; /** * The {@link SnmpDatatype} enum defines the datatype of SNMP channels * * @author Jan N. Klug - Initial contribution */ - +@NonNullByDefault public enum SnmpDatatype { INT32, UINT32, diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpPrivProtocol.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpPrivProtocol.java new file mode 100644 index 0000000000000..c7caecc436677 --- /dev/null +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpPrivProtocol.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.snmp.internal.types; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.snmp4j.security.PrivAES128; +import org.snmp4j.security.PrivAES192; +import org.snmp4j.security.PrivAES256; +import org.snmp4j.security.PrivDES; +import org.snmp4j.smi.OID; + +/** + * The {@link SnmpPrivProtocol} enum defines the possible privacy protocols for v3 + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public enum SnmpPrivProtocol { + AES128(PrivAES128.ID), + AES192(PrivAES192.ID), + AES256(PrivAES256.ID), + DES(PrivDES.ID); + + private final OID oid; + + SnmpPrivProtocol(OID oid) { + this.oid = oid; + } + + /** + * get the OID for this privacy protocol + * + * @return the corresponding OID + */ + public OID getOid() { + return oid; + } +} diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpProtocolVersion.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpProtocolVersion.java similarity index 78% rename from bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpProtocolVersion.java rename to bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpProtocolVersion.java index 7700dedf802b9..0cfaf593e9189 100644 --- a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpProtocolVersion.java +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpProtocolVersion.java @@ -10,23 +10,27 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.snmp.internal; +package org.openhab.binding.snmp.internal.types; + +import org.eclipse.jdt.annotation.NonNullByDefault; /** * The {@link SnmpProtocolVersion} enum defines the datatype of SNMP channels * * @author Jan N. Klug - Initial contribution */ - +@NonNullByDefault public enum SnmpProtocolVersion { v1(0), V1(0), v2c(1), - V2C(1); + V2C(1), + v3(3), + V3(3); private final int value; - private SnmpProtocolVersion(int value) { + SnmpProtocolVersion(int value) { this.value = value; } diff --git a/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpSecurityModel.java b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpSecurityModel.java new file mode 100644 index 0000000000000..c20e84dc69642 --- /dev/null +++ b/bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpSecurityModel.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.snmp.internal.types; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.snmp4j.security.SecurityLevel; + +/** + * The {@link SnmpSecurityModel} enum defines the security model for v3 + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public enum SnmpSecurityModel { + NO_AUTH_NO_PRIV(SecurityLevel.NOAUTH_NOPRIV), + AUTH_NO_PRIV(SecurityLevel.AUTH_NOPRIV), + AUTH_PRIV(SecurityLevel.AUTH_PRIV); + + private final int securityLevel; + + SnmpSecurityModel(int securityLevel) { + this.securityLevel = securityLevel; + } + + /** + * get the numeric security level + * + * @return the int representing this security level + */ + public int getSecurityLevel() { + return securityLevel; + } +} diff --git a/bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/i18n/snmp.properties b/bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/i18n/snmp.properties index 736e0a4b3896a..ee531d1c7690d 100644 --- a/bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/i18n/snmp.properties +++ b/bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/i18n/snmp.properties @@ -11,6 +11,7 @@ addon.config.snmp.port.description = Port for receiving traps, set to 0 to disab # thing types thing-type.snmp.target.label = SNMP Target +thing-type.snmp.target3.label = SNMP v3 Target # thing types config @@ -27,6 +28,36 @@ thing-type.config.snmp.target.retries.label = Retries thing-type.config.snmp.target.retries.description = Number of retries for an update request thing-type.config.snmp.target.timeout.label = Timeout thing-type.config.snmp.target.timeout.description = Timeout in ms for a single update request +thing-type.config.snmp.target3.authPassphrase.label = Authentication Passphrase +thing-type.config.snmp.target3.authProtocol.label = Authentication Protocol +thing-type.config.snmp.target3.authProtocol.option.MD5 = MD5 +thing-type.config.snmp.target3.authProtocol.option.SHA = SHA +thing-type.config.snmp.target3.authProtocol.option.HMAC128SHA224 = HMAC128SHA224 +thing-type.config.snmp.target3.authProtocol.option.HMAC192SHA256 = HMAC192SHA256 +thing-type.config.snmp.target3.authProtocol.option.HMAC256SHA384 = HMAC256SHA384 +thing-type.config.snmp.target3.authProtocol.option.HMAC384SHA512 = HMAC384SHA512 +thing-type.config.snmp.target3.engineId.label = Engine ID +thing-type.config.snmp.target3.engineId.description = The authorization engine ID of this target in hexadecimal notation (22-64 characters) +thing-type.config.snmp.target3.hostname.label = Target Host +thing-type.config.snmp.target3.hostname.description = Hostname or IP address of target host +thing-type.config.snmp.target3.port.label = Port +thing-type.config.snmp.target3.privPassphrase.label = Privacy Passphrase +thing-type.config.snmp.target3.privProtocol.label = Privacy Protocol +thing-type.config.snmp.target3.privProtocol.option.AES128 = AES128 +thing-type.config.snmp.target3.privProtocol.option.AES192 = AES192 +thing-type.config.snmp.target3.privProtocol.option.AES256 = AES256 +thing-type.config.snmp.target3.privProtocol.option.DES = DES +thing-type.config.snmp.target3.refresh.label = Refresh Time +thing-type.config.snmp.target3.refresh.description = Refresh time in s (default 60s) +thing-type.config.snmp.target3.retries.label = Retries +thing-type.config.snmp.target3.retries.description = Number of retries for an update request +thing-type.config.snmp.target3.securityModel.label = Security Model +thing-type.config.snmp.target3.securityModel.option.NO_AUTH_NO_PRIV = No authentication and no Privacy +thing-type.config.snmp.target3.securityModel.option.AUTH_NO_PRIV = Authentication and no Privacy +thing-type.config.snmp.target3.securityModel.option.AUTH_PRIV = Authentication and Privacy +thing-type.config.snmp.target3.timeout.label = Timeout +thing-type.config.snmp.target3.timeout.description = Timeout in ms for a single update request +thing-type.config.snmp.target3.user.label = Username # channel types diff --git a/bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/thing/thing-types.xml index a34b75cb7746e..782d70d6fe065 100644 --- a/bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/thing/thing-types.xml @@ -52,7 +52,92 @@ true + + + + + + + + + Hostname or IP address of target host + network-address + + + + The authorization engine ID of this target in hexadecimal notation (22-64 characters) + + + + + + + + + + + + + true + NO_AUTH_NO_PRIV + + + + + + + + + + + + true + MD5 + + + + password + + + + + + + + + + true + DES + + + + password + + + + Refresh time in s (default 60s) + 60 + + + + + 161 + true + + + + Timeout in ms for a single update request + 1500 + true + + + + Number of retries for an update request + 2 + true + + @@ -76,6 +161,10 @@ READ true + + + The unit of this value. + Content data type @@ -99,12 +188,6 @@ Value to send if an SNMP exception occurs (default: UNDEF) true - - - 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" - true - diff --git a/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/AbstractSnmpTargetHandlerTest.java b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/AbstractSnmpTargetHandlerTest.java index e6de64a631887..ac46fd4320309 100644 --- a/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/AbstractSnmpTargetHandlerTest.java +++ b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/AbstractSnmpTargetHandlerTest.java @@ -23,10 +23,14 @@ import java.util.Map; import java.util.Vector; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.AfterEach; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.openhab.binding.snmp.internal.types.SnmpChannelMode; +import org.openhab.binding.snmp.internal.types.SnmpDatatype; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.StringType; import org.openhab.core.test.java.JavaTest; @@ -53,6 +57,7 @@ * * @author Jan N. Klug - Initial contribution */ +@NonNullByDefault public abstract class AbstractSnmpTargetHandlerTest extends JavaTest { protected static final ThingUID THING_UID = new ThingUID(THING_TYPE_TARGET, "testthing"); protected static final ChannelUID CHANNEL_UID = new ChannelUID(THING_UID, "testchannel"); @@ -60,20 +65,20 @@ public abstract class AbstractSnmpTargetHandlerTest extends JavaTest { protected static final String TEST_ADDRESS = "192.168.0.1"; protected static final String TEST_STRING = "foo."; - protected @Mock SnmpServiceImpl snmpService; - protected @Mock ThingHandlerCallback thingHandlerCallback; + protected @Mock @NonNullByDefault({}) SnmpServiceImpl snmpService; + protected @Mock @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback; - protected Thing thing; - protected SnmpTargetHandler thingHandler; - private AutoCloseable mocks; + protected @NonNullByDefault({}) Thing thing; + protected @NonNullByDefault({}) SnmpTargetHandler thingHandler; + private @NonNullByDefault({}) AutoCloseable mocks; @AfterEach public void after() throws Exception { mocks.close(); } - protected VariableBinding handleCommandSwitchChannel(SnmpDatatype datatype, Command command, String onValue, - String offValue, boolean refresh) throws IOException { + protected @Nullable VariableBinding handleCommandSwitchChannel(SnmpDatatype datatype, Command command, + String onValue, @Nullable String offValue, boolean refresh) throws IOException { setup(SnmpBindingConstants.CHANNEL_TYPE_UID_SWITCH, SnmpChannelMode.WRITE, datatype, onValue, offValue); thingHandler.handleCommand(CHANNEL_UID, command); @@ -87,9 +92,14 @@ protected VariableBinding handleCommandSwitchChannel(SnmpDatatype datatype, Comm } } - protected VariableBinding handleCommandNumberStringChannel(ChannelTypeUID channelTypeUID, SnmpDatatype datatype, - Command command, boolean refresh) throws IOException { - setup(channelTypeUID, SnmpChannelMode.WRITE, datatype); + protected @Nullable VariableBinding handleCommandNumberStringChannel(ChannelTypeUID channelTypeUID, + SnmpDatatype datatype, Command command, boolean refresh) throws IOException { + return handleCommandNumberStringChannel(channelTypeUID, datatype, null, command, refresh); + } + + protected @Nullable VariableBinding handleCommandNumberStringChannel(ChannelTypeUID channelTypeUID, + SnmpDatatype datatype, @Nullable String unit, Command command, boolean refresh) throws IOException { + setup(channelTypeUID, SnmpChannelMode.WRITE, datatype, null, null, null, unit); thingHandler.handleCommand(CHANNEL_UID, command); if (refresh) { @@ -118,8 +128,8 @@ protected void onResponseNumberStringChannel(SnmpChannelMode channelMode, boolea } } - protected State onResponseSwitchChannel(SnmpChannelMode channelMode, SnmpDatatype datatype, String onValue, - String offValue, Variable value, boolean refresh) { + protected @Nullable State onResponseSwitchChannel(SnmpChannelMode channelMode, SnmpDatatype datatype, + String onValue, String offValue, Variable value, boolean refresh) { setup(SnmpBindingConstants.CHANNEL_TYPE_UID_SWITCH, channelMode, datatype, onValue, offValue); PDU responsePDU = new PDU(PDU.RESPONSE, @@ -159,22 +169,23 @@ protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode) setup(channelTypeUID, channelMode, null); } - protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, SnmpDatatype datatype) { + protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, @Nullable SnmpDatatype datatype) { setup(channelTypeUID, channelMode, datatype, null, null); } - protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, SnmpDatatype datatype, - String onValue, String offValue) { + protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, @Nullable SnmpDatatype datatype, + @Nullable String onValue, @Nullable String offValue) { setup(channelTypeUID, channelMode, datatype, onValue, offValue, null); } - protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, SnmpDatatype datatype, - String onValue, String offValue, String exceptionValue) { + protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, @Nullable SnmpDatatype datatype, + @Nullable String onValue, @Nullable String offValue, @Nullable 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) { + protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, @Nullable SnmpDatatype datatype, + @Nullable String onValue, @Nullable String offValue, @Nullable String exceptionValue, + @Nullable String unit) { Map channelConfig = new HashMap<>(); Map thingConfig = new HashMap<>(); mocks = MockitoAnnotations.openMocks(this); @@ -184,29 +195,27 @@ protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, ThingBuilder thingBuilder = ThingBuilder.create(THING_TYPE_TARGET, THING_UID).withLabel("Test thing") .withConfiguration(new Configuration(thingConfig)); - if (channelTypeUID != null && channelMode != null) { - String itemType = SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER.equals(channelTypeUID) ? "Number" : "String"; - channelConfig.put("oid", TEST_OID); - channelConfig.put("mode", channelMode.name()); - if (datatype != null) { - channelConfig.put("datatype", datatype.name()); - } - if (onValue != null) { - channelConfig.put("onvalue", onValue); - } - if (offValue != null) { - channelConfig.put("offvalue", offValue); - } - 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); + String itemType = SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER.equals(channelTypeUID) ? "Number" : "String"; + channelConfig.put("oid", TEST_OID); + channelConfig.put("mode", channelMode.name()); + if (datatype != null) { + channelConfig.put("datatype", datatype.name()); + } + if (onValue != null) { + channelConfig.put("onvalue", onValue); + } + if (offValue != null) { + channelConfig.put("offvalue", offValue); + } + 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); thing = thingBuilder.build(); thingHandler = new SnmpTargetHandler(thing, snmpService); diff --git a/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/NumberChannelTest.java b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/NumberChannelTest.java new file mode 100644 index 0000000000000..e9544bf04ade4 --- /dev/null +++ b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/NumberChannelTest.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.snmp.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.openhab.binding.snmp.internal.types.SnmpChannelMode; +import org.openhab.binding.snmp.internal.types.SnmpDatatype; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.thing.ThingStatus; +import org.snmp4j.PDU; +import org.snmp4j.event.ResponseEvent; +import org.snmp4j.smi.Counter64; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.Opaque; +import org.snmp4j.smi.VariableBinding; + +/** + * Tests cases for {@link SnmpTargetHandler}. + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class NumberChannelTest extends AbstractSnmpTargetHandlerTest { + + @Test + public void testNumberChannelsProperlyUpdatingOnOpaque() { + setup(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpChannelMode.READ, SnmpDatatype.FLOAT); + PDU responsePDU = new PDU(PDU.RESPONSE, List.of(new VariableBinding(new OID(TEST_OID), + new Opaque(new byte[] { (byte) 0x9f, 0x78, 0x04, 0x41, 0x5b, 0x33, 0x33 })))); + ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null); + thingHandler.onResponse(event); + final ArgumentCaptor captor = ArgumentCaptor.forClass(DecimalType.class); + verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), captor.capture()); + assertEquals(13.7, captor.getValue().doubleValue(), 0.001); + verifyStatus(ThingStatus.ONLINE); + } + + @Test + public void testNumberChannelsProperlyUpdatingOnInteger() { + setup(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpChannelMode.READ, SnmpDatatype.COUNTER64); + PDU responsePDU = new PDU(PDU.RESPONSE, + List.of(new VariableBinding(new OID(TEST_OID), new Counter64(1234567891333L)))); + ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null); + thingHandler.onResponse(event); + verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), eq(new DecimalType(1234567891333L))); + verifyStatus(ThingStatus.ONLINE); + } + + @Test + public void testNumberChannelsProperlyUpdatingOnQuantityType() { + setup(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpChannelMode.READ, SnmpDatatype.FLOAT, null, null, null, + "°C"); + PDU responsePDU = new PDU(PDU.RESPONSE, List.of(new VariableBinding(new OID(TEST_OID), + new Opaque(new byte[] { (byte) 0x9f, 0x78, 0x04, 0x41, 0x5b, 0x33, 0x33 })))); + ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null); + thingHandler.onResponse(event); + final ArgumentCaptor> captor = ArgumentCaptor.forClass(QuantityType.class); + verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), captor.capture()); + assertEquals(13.7, captor.getValue().doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, captor.getValue().getUnit()); + verifyStatus(ThingStatus.ONLINE); + } +} diff --git a/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/SnmpTargetHandlerTest.java b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/SnmpTargetHandlerTest.java index 347b267e6c350..e4b5ac670cb5f 100644 --- a/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/SnmpTargetHandlerTest.java +++ b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/SnmpTargetHandlerTest.java @@ -19,12 +19,15 @@ import java.io.IOException; import java.util.Collections; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.Test; +import org.openhab.binding.snmp.internal.types.SnmpChannelMode; +import org.openhab.binding.snmp.internal.types.SnmpDatatype; 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; @@ -41,6 +44,7 @@ * * @author Jan N. Klug - Initial contribution */ +@NonNullByDefault public class SnmpTargetHandlerTest extends AbstractSnmpTargetHandlerTest { @Test @@ -52,7 +56,7 @@ public void testChannelsProperlyRefreshing() throws IOException { } @Test - public void testChannelsProperlyUpdate() throws IOException { + public void testChannelsProperlyUpdate() { onResponseNumberStringChannel(SnmpChannelMode.READ, true); onResponseNumberStringChannel(SnmpChannelMode.READ_WRITE, true); onResponseNumberStringChannel(SnmpChannelMode.WRITE, false); @@ -73,30 +77,66 @@ public void testCommandsAreProperlyHandledByNumberChannel() throws IOException { VariableBinding variable; variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpDatatype.INT32, new DecimalType(-5), true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + assertEquals(new OID(TEST_OID), variable.getOid()); assertTrue(variable.getVariable() instanceof Integer32); assertEquals(-5, ((Integer32) variable.getVariable()).toInt()); variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpDatatype.UINT32, new DecimalType(10000), true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + assertEquals(new OID(TEST_OID), variable.getOid()); assertTrue(variable.getVariable() instanceof UnsignedInteger32); assertEquals(10000, ((UnsignedInteger32) variable.getVariable()).toInt()); variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpDatatype.COUNTER64, new DecimalType(10000), true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + assertEquals(new OID(TEST_OID), variable.getOid()); assertTrue(variable.getVariable() instanceof Counter64); assertEquals(10000, ((Counter64) variable.getVariable()).toInt()); variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpDatatype.FLOAT, new DecimalType("12.4"), true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + assertEquals(new OID(TEST_OID), variable.getOid()); assertTrue(variable.getVariable() instanceof OctetString); assertEquals("12.4", variable.getVariable().toString()); + variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpDatatype.FLOAT, + "°C", new QuantityType<>("50 °F"), true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + + assertEquals(new OID(TEST_OID), variable.getOid()); + assertTrue(variable.getVariable() instanceof OctetString); + assertEquals("10.00", variable.getVariable().toString().substring(0, 5)); + variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpDatatype.INT32, - new StringType(TEST_STRING), false); + null, new StringType(TEST_STRING), false); assertNull(variable); } @@ -111,19 +151,6 @@ public void testNumberChannelsProperlyUpdatingFloatValue() throws IOException { 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); @@ -139,11 +166,11 @@ public void testCancelingAsyncRequest() { verifyStatus(ThingStatus.ONLINE); } - class SnmpMock extends Snmp { + static class SnmpMock extends Snmp { public int cancelCallCounter = 0; @Override - public void cancel(PDU request, org.snmp4j.event.ResponseListener listener) { + public void cancel(@Nullable PDU request, org.snmp4j.event.@Nullable ResponseListener listener) { ++cancelCallCounter; } } diff --git a/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/StringChannelTest.java b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/StringChannelTest.java index d8136e2428e7e..4d09dd3c46587 100644 --- a/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/StringChannelTest.java +++ b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/StringChannelTest.java @@ -19,7 +19,10 @@ import java.io.IOException; import java.util.Collections; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; +import org.openhab.binding.snmp.internal.types.SnmpChannelMode; +import org.openhab.binding.snmp.internal.types.SnmpDatatype; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.ThingStatus; @@ -35,6 +38,7 @@ * * @author Jan N. Klug - Initial contribution */ +@NonNullByDefault public class StringChannelTest extends AbstractSnmpTargetHandlerTest { @Test @@ -43,6 +47,12 @@ public void testCommandsAreProperlyHandledByStringChannel() throws IOException { variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_STRING, SnmpDatatype.STRING, new StringType(TEST_STRING), true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + assertEquals(new OID(TEST_OID), variable.getOid()); assertTrue(variable.getVariable() instanceof OctetString); assertEquals(TEST_STRING, ((OctetString) variable.getVariable()).toString()); @@ -57,12 +67,24 @@ public void testCommandsAreProperlyHandledByStringChannel() throws IOException { variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_STRING, SnmpDatatype.HEXSTRING, new StringType("AA bf 11"), true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + assertEquals(new OID(TEST_OID), variable.getOid()); assertTrue(variable.getVariable() instanceof OctetString); assertEquals("aa bf 11", ((OctetString) variable.getVariable()).toHexString(' ')); variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_STRING, SnmpDatatype.IPADDRESS, new StringType(TEST_ADDRESS), true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + assertEquals(new OID(TEST_OID), variable.getOid()); assertTrue(variable.getVariable() instanceof IpAddress); assertEquals(TEST_ADDRESS, ((IpAddress) variable.getVariable()).toString()); diff --git a/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/SwitchChannelTest.java b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/SwitchChannelTest.java index 3227eeda316e4..0ae04d1446978 100644 --- a/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/SwitchChannelTest.java +++ b/bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/SwitchChannelTest.java @@ -19,7 +19,10 @@ import java.io.IOException; import java.util.Collections; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; +import org.openhab.binding.snmp.internal.types.SnmpChannelMode; +import org.openhab.binding.snmp.internal.types.SnmpDatatype; import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.ThingStatus; import org.openhab.core.types.State; @@ -38,6 +41,7 @@ * * @author Jan N. Klug - Initial contribution */ +@NonNullByDefault public class SwitchChannelTest extends AbstractSnmpTargetHandlerTest { @Test @@ -45,11 +49,23 @@ public void testCommandsAreProperlyHandledBySwitchChannel() throws IOException { VariableBinding variable; variable = handleCommandSwitchChannel(SnmpDatatype.STRING, OnOffType.ON, "on", "off", true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + assertEquals(new OID(TEST_OID), variable.getOid()); assertTrue(variable.getVariable() instanceof OctetString); assertEquals("on", ((OctetString) variable.getVariable()).toString()); variable = handleCommandSwitchChannel(SnmpDatatype.STRING, OnOffType.OFF, "on", "off", true); + + if (variable == null) { + fail("'variable' is null"); + return; + } + assertEquals(new OID(TEST_OID), variable.getOid()); assertTrue(variable.getVariable() instanceof OctetString); assertEquals("off", ((OctetString) variable.getVariable()).toString());