/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
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
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
## 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.
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.
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).
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
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" ]
```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" }
// 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");
+++ /dev/null
-/**
- * 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;
-
-/**
- * The {@link SnmpChannelMode} enum defines the mode of SNMP channels
- *
- * @author Jan N. Klug - Initial contribution
- */
-
-public enum SnmpChannelMode {
- READ,
- WRITE,
- READ_WRITE,
- TRAP
-}
+++ /dev/null
-/**
- * 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;
-
-/**
- * The {@link SnmpDatatype} enum defines the datatype of SNMP channels
- *
- * @author Jan N. Klug - Initial contribution
- */
-
-public enum SnmpDatatype {
- INT32,
- UINT32,
- COUNTER64,
- FLOAT,
- STRING,
- HEXSTRING,
- IPADDRESS
-}
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;
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.snmp")
public class SnmpHandlerFactory extends BaseThingHandlerFactory {
- private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_TARGET);
+ private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_TARGET, THING_TYPE_TARGET3);
private final SnmpService snmpService;
@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;
+++ /dev/null
-/**
- * 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;
-
-/**
- * The {@link SnmpProtocolVersion} enum defines the datatype of SNMP channels
- *
- * @author Jan N. Klug - Initial contribution
- */
-
-public enum SnmpProtocolVersion {
- v1(0),
- V1(0),
- v2c(1),
- V2C(1);
-
- private final int value;
-
- private SnmpProtocolVersion(int value) {
- this.value = value;
- }
-
- public int toInteger() {
- return value;
- }
-}
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;
@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);
}
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;
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;
private @Nullable Snmp snmp;
private @Nullable DefaultUdpTransportMapping transport;
- private List<CommandResponder> listeners = new ArrayList<>();
+ private final List<CommandResponder> listeners = new ArrayList<>();
+ private final Set<UserEntry> userEntries = new HashSet<>();
@Activate
public SnmpServiceImpl(Map<String, Object> 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);
}
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;
}
}
+ @SuppressWarnings("unused")
@Deactivate
public void deactivate() {
try {
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;
+ }
+ }
}
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;
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;
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;
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;
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;
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"));
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) {
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);
}
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) {
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) {
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())) {
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<SnmpInternalChannelConfiguration> channelConfigs = Collections
- .unmodifiableSet(thing.getChannels().stream().map(channel -> getChannelConfigFromChannel(channel))
- .filter(Objects::nonNull).collect(Collectors.toSet()));
+ Set<SnmpInternalChannelConfiguration> 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());
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) {
- 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);
private Variable convertDatatype(Command command, SnmpDatatype datatype) {
switch (datatype) {
- case INT32:
+ case INT32 -> {
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);
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);
}
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);
}
}
}
+
+ private PDU getPDU() {
+ if (config.protocol == SnmpProtocolVersion.v3 || config.protocol == SnmpProtocolVersion.V3) {
+ return new ScopedPDU();
+ } else {
+ return new PDU();
+ }
+ }
}
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.
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;
}
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;
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;
this.onValue = onValue;
this.offValue = offValue;
this.exceptionValue = exceptionValue;
- this.doNotLogException = doNotLogException;
this.unit = unit;
+ this.doNotLogException = doNotLogException;
}
}
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.
*/
@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;
}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+
+/**
+ * The {@link SnmpChannelMode} enum defines the mode of SNMP channels
+ *
+ * @author Jan N. Klug - Initial contribution
+ */
+@NonNullByDefault
+public enum SnmpChannelMode {
+ READ,
+ WRITE,
+ READ_WRITE,
+ TRAP
+}
--- /dev/null
+/**
+ * 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;
+
+/**
+ * The {@link SnmpDatatype} enum defines the datatype of SNMP channels
+ *
+ * @author Jan N. Klug - Initial contribution
+ */
+@NonNullByDefault
+public enum SnmpDatatype {
+ INT32,
+ UINT32,
+ COUNTER64,
+ FLOAT,
+ STRING,
+ HEXSTRING,
+ IPADDRESS
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+
+/**
+ * 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),
+ v3(3),
+ V3(3);
+
+ private final int value;
+
+ SnmpProtocolVersion(int value) {
+ this.value = value;
+ }
+
+ public int toInteger() {
+ return value;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
# thing types
thing-type.snmp.target.label = SNMP Target
+thing-type.snmp.target3.label = SNMP v3 Target
# thing types config
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
<advanced>true</advanced>
</parameter>
</config-description>
+ </thing-type>
+
+ <thing-type id="target3" extensible="number,string,switch">
+ <label>SNMP v3 Target</label>
+ <config-description>
+ <!-- required -->
+ <parameter name="hostname" type="text" required="true">
+ <label>Target Host</label>
+ <description>Hostname or IP address of target host</description>
+ <context>network-address</context>
+ </parameter>
+ <parameter name="engineId" type="text" required="true">
+ <label>Engine ID</label>
+ <description>The authorization engine ID of this target in hexadecimal notation (22-64 characters)</description>
+ </parameter>
+ <parameter name="user" type="text" required="true">
+ <label>Username</label>
+ </parameter>
+ <!-- optional -->
+ <parameter name="securityModel" type="text">
+ <label>Security Model</label>
+ <options>
+ <option value="NO_AUTH_NO_PRIV">No authentication and no Privacy</option>
+ <option value="AUTH_NO_PRIV">Authentication and no Privacy</option>
+ <option value="AUTH_PRIV">Authentication and Privacy</option>
+ </options>
+ <limitToOptions>true</limitToOptions>
+ <default>NO_AUTH_NO_PRIV</default>
+ </parameter>
+ <parameter name="authProtocol" type="text">
+ <label>Authentication Protocol</label>
+ <options>
+ <option value="MD5">MD5</option>
+ <option value="SHA">SHA</option>
+ <option value="HMAC128SHA224">HMAC128SHA224</option>
+ <option value="HMAC192SHA256">HMAC192SHA256</option>
+ <option value="HMAC256SHA384">HMAC256SHA384</option>
+ <option value="HMAC384SHA512">HMAC384SHA512</option>
+ </options>
+ <limitToOptions>true</limitToOptions>
+ <default>MD5</default>
+ </parameter>
+ <parameter name="authPassphrase" type="text">
+ <label>Authentication Passphrase</label>
+ <context>password</context>
+ </parameter>
+ <parameter name="privProtocol" type="text">
+ <label>Privacy Protocol</label>
+ <options>
+ <option value="AES128">AES128</option>
+ <option value="AES192">AES192</option>
+ <option value="AES256">AES256</option>
+ <option value="DES">DES</option>
+ </options>
+ <limitToOptions>true</limitToOptions>
+ <default>DES</default>
+ </parameter>
+ <parameter name="privPassphrase" type="text">
+ <label>Privacy Passphrase</label>
+ <context>password</context>
+ </parameter>
+ <parameter name="refresh" type="integer" min="1">
+ <label>Refresh Time</label>
+ <description>Refresh time in s (default 60s)</description>
+ <default>60</default>
+ </parameter>
+ <!-- optional advanced -->
+ <parameter name="port" type="integer">
+ <label>Port</label>
+ <default>161</default>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="timeout" type="integer" min="0">
+ <label>Timeout</label>
+ <description>Timeout in ms for a single update request</description>
+ <default>1500</default>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="retries" type="integer" min="0">
+ <label>Retries</label>
+ <description>Number of retries for an update request</description>
+ <default>2</default>
+ <advanced>true</advanced>
+ </parameter>
+ </config-description>
</thing-type>
<channel-type id="number">
<default>READ</default>
<limitToOptions>true</limitToOptions>
</parameter>
+ <parameter name="unit" type="text">
+ <label>Unit</label>
+ <description>The unit of this value.</description>
+ </parameter>
<parameter name="datatype" type="text">
<label>Datatype</label>
<description>Content data type</description>
<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>
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;
*
* @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");
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);
}
}
- 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) {
}
}
- 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,
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<String, Object> channelConfig = new HashMap<>();
Map<String, Object> thingConfig = new HashMap<>();
mocks = MockitoAnnotations.openMocks(this);
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);
--- /dev/null
+/**
+ * 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<DecimalType> 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<QuantityType<?>> 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);
+ }
+}
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;
*
* @author Jan N. Klug - Initial contribution
*/
+@NonNullByDefault
public class SnmpTargetHandlerTest extends AbstractSnmpTargetHandlerTest {
@Test
}
@Test
- public void testChannelsProperlyUpdate() throws IOException {
+ public void testChannelsProperlyUpdate() {
onResponseNumberStringChannel(SnmpChannelMode.READ, true);
onResponseNumberStringChannel(SnmpChannelMode.READ_WRITE, true);
onResponseNumberStringChannel(SnmpChannelMode.WRITE, false);
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);
}
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);
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;
}
}
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;
*
* @author Jan N. Klug - Initial contribution
*/
+@NonNullByDefault
public class StringChannelTest extends AbstractSnmpTargetHandlerTest {
@Test
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());
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());
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;
*
* @author Jan N. Klug - Initial contribution
*/
+@NonNullByDefault
public class SwitchChannelTest extends AbstractSnmpTargetHandlerTest {
@Test
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());