mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[snmp] Upgrades and enhancements (#14330)
* [snmp] Upgrades and enhancements - bug: improve test stability - enhancement: add support for UoM - bug: fix misleading error message - bug: fix initialization exceptions - enhancement: Add support for SNMPv3 - enhancement: add opaque value handling Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
parent
8a4033c95f
commit
fc57f02fca
@ -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
|
||||
|
@ -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.
|
||||
### Common parameters for all thing-types
|
||||
|
||||
Optional configuration parameters are `community`, `version` and `refresh`.
|
||||
|
||||
The SNMP community 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`.
|
||||
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" }
|
||||
|
@ -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");
|
||||
|
@ -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 @@ import org.osgi.service.component.annotations.Reference;
|
||||
@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;
|
||||
|
||||
@ -54,7 +54,7 @@ public class SnmpHandlerFactory extends BaseThingHandlerFactory {
|
||||
@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;
|
||||
|
@ -16,6 +16,8 @@ import java.io.IOException;
|
||||
|
||||
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 @@ import org.snmp4j.event.ResponseListener;
|
||||
@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);
|
||||
}
|
||||
|
@ -14,12 +14,16 @@ package org.openhab.binding.snmp.internal;
|
||||
|
||||
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.PDU;
|
||||
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<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);
|
||||
}
|
||||
|
||||
@ -78,9 +95,12 @@ public class SnmpServiceImpl implements SnmpService {
|
||||
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 @@ public class SnmpServiceImpl implements SnmpService {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
try {
|
||||
@ -141,4 +162,37 @@ public class SnmpServiceImpl implements SnmpService {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ package org.openhab.binding.snmp.internal;
|
||||
import static org.openhab.binding.snmp.internal.SnmpBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
@ -28,13 +27,16 @@ import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.format.MeasurementParseException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.snmp.internal.config.SnmpChannelConfiguration;
|
||||
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.RefreshType;
|
||||
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.CommandResponderEvent;
|
||||
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.Integer32;
|
||||
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 class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
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 class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
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 class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
|
||||
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 class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
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 class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
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 class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
if (configExceptionValue != null) {
|
||||
exceptionValue = DecimalType.valueOf(configExceptionValue);
|
||||
}
|
||||
if (config.unit != null) {
|
||||
if (config.mode != SnmpChannelMode.READ) {
|
||||
logger.warn("units only supported for readonly channels, ignored for channel {}", channel.getUID());
|
||||
} else {
|
||||
try {
|
||||
unit = UnitUtils.parseUnit(config.unit);
|
||||
} catch (MeasurementParseException e) {
|
||||
logger.warn("unrecognised unit '{}', ignored for channel '{}'", config.unit, channel.getUID());
|
||||
}
|
||||
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 class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
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());
|
||||
@ -359,17 +425,37 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
state = channelConfig.exceptionValue;
|
||||
} else if (CHANNEL_TYPE_UID_NUMBER.equals(channel.getChannelTypeUID())) {
|
||||
try {
|
||||
BigDecimal numericState;
|
||||
final @Nullable Unit<?> unit = channelConfig.unit;
|
||||
if (channelConfig.datatype == SnmpDatatype.FLOAT) {
|
||||
numericState = new BigDecimal(value.toString());
|
||||
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 {
|
||||
numericState = BigDecimal.valueOf(value.toLong());
|
||||
}
|
||||
if (unit != null) {
|
||||
state = new QuantityType<>(numericState, unit);
|
||||
} 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 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
|
||||
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);
|
||||
@ -442,13 +527,14 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
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 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
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 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PDU getPDU() {
|
||||
if (config.protocol == SnmpProtocolVersion.v3 || config.protocol == SnmpProtocolVersion.V3) {
|
||||
return new ScopedPDU();
|
||||
} else {
|
||||
return new PDU();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,8 @@ package org.openhab.binding.snmp.internal.config;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ import javax.measure.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;
|
||||
@ -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 class SnmpInternalChannelConfiguration {
|
||||
this.onValue = onValue;
|
||||
this.offValue = offValue;
|
||||
this.exceptionValue = exceptionValue;
|
||||
this.doNotLogException = doNotLogException;
|
||||
this.unit = unit;
|
||||
this.doNotLogException = doNotLogException;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,10 @@ package org.openhab.binding.snmp.internal.config;
|
||||
|
||||
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 @@ import org.openhab.binding.snmp.internal.SnmpProtocolVersion;
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
@ -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,
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -52,7 +52,92 @@
|
||||
<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">
|
||||
@ -76,6 +161,10 @@
|
||||
<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>
|
||||
@ -99,12 +188,6 @@
|
||||
<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>
|
||||
|
||||
|
@ -23,10 +23,14 @@ import java.util.HashMap;
|
||||
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 @@ import org.snmp4j.smi.VariableBinding;
|
||||
*
|
||||
* @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 @@ public abstract class AbstractSnmpTargetHandlerTest extends JavaTest {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public abstract class AbstractSnmpTargetHandlerTest extends JavaTest {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public abstract class AbstractSnmpTargetHandlerTest extends JavaTest {
|
||||
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);
|
||||
@ -184,29 +195,27 @@ public abstract class AbstractSnmpTargetHandlerTest extends JavaTest {
|
||||
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);
|
||||
|
@ -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<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);
|
||||
}
|
||||
}
|
@ -19,12 +19,15 @@ import static org.mockito.Mockito.*;
|
||||
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 @@ import org.snmp4j.smi.VariableBinding;
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SnmpTargetHandlerTest extends AbstractSnmpTargetHandlerTest {
|
||||
|
||||
@Test
|
||||
@ -52,7 +56,7 @@ public class SnmpTargetHandlerTest extends AbstractSnmpTargetHandlerTest {
|
||||
}
|
||||
|
||||
@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 class SnmpTargetHandlerTest extends AbstractSnmpTargetHandlerTest {
|
||||
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 class SnmpTargetHandlerTest extends AbstractSnmpTargetHandlerTest {
|
||||
verifyStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumberChannelsProperlyHandlingUnits() throws IOException {
|
||||
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpChannelMode.READ, SnmpDatatype.FLOAT, null, null, null,
|
||||
"°C");
|
||||
PDU responsePDU = new PDU(PDU.RESPONSE,
|
||||
Collections.singletonList(new VariableBinding(new OID(TEST_OID), new OctetString("12.4"))));
|
||||
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
|
||||
thingHandler.onResponse(event);
|
||||
verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID),
|
||||
eq(new QuantityType<>(12.4, SIUnits.CELSIUS)));
|
||||
verifyStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelingAsyncRequest() {
|
||||
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpChannelMode.READ, SnmpDatatype.FLOAT);
|
||||
@ -139,11 +166,11 @@ public class SnmpTargetHandlerTest extends AbstractSnmpTargetHandlerTest {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,10 @@ import static org.mockito.Mockito.*;
|
||||
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 @@ import org.snmp4j.smi.VariableBinding;
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StringChannelTest extends AbstractSnmpTargetHandlerTest {
|
||||
|
||||
@Test
|
||||
@ -43,6 +47,12 @@ public class StringChannelTest extends AbstractSnmpTargetHandlerTest {
|
||||
|
||||
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 class StringChannelTest extends AbstractSnmpTargetHandlerTest {
|
||||
|
||||
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());
|
||||
|
@ -19,7 +19,10 @@ import static org.mockito.Mockito.*;
|
||||
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 @@ import org.snmp4j.smi.VariableBinding;
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SwitchChannelTest extends AbstractSnmpTargetHandlerTest {
|
||||
|
||||
@Test
|
||||
@ -45,11 +49,23 @@ public class SwitchChannelTest extends AbstractSnmpTargetHandlerTest {
|
||||
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());
|
||||
|
Loading…
Reference in New Issue
Block a user