added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.snmp</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,20 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
== Third-party Content
SNMP4J:
* License: Apache 2.0 License
* Project: http://www.snmp4j.org
* Source: hhttp://www.snmp4j.org/html/download.html

View File

@@ -0,0 +1,162 @@
# SNMP Binding
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.
## Supported Things
Only one thing is supported: `target`.
It represents a single network device.
Things can be extended with `number`, `string` and `switch` channels.
## Binding Configuration
If only GET/SET-requests shall be used, no configuration is necessary.
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).
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
```
iptables -t nat -I PREROUTING --src 0/0 --dst 192.168.0.10 -p udp --dport 162 -j REDIRECT --to-ports 8162
```
would forward all TCP packets addressed to 192.168.0.10 from port 162 to 8162.
Check with your operating system manual how to make that change permanent.
Example configuration for using port 8162:
```
# Configuration for the SNMP Binding
#
# Port used for receiving traps.
# This setting defaults to 0 (disabled / not receiving traps)
port=8162
```
## Thing Configuration
The `target` thing has one mandatory parameter: `hostname`.
It can be set as FQDN or IP address.
Optional configuration parameters are `community`, `version` and `refresh`.
The SNMP community can be set with the `community` parameter.
It defaults to `public`.
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`.
By using the `refresh` parameter the time between two subsequent GET requests to the target can be set.
The default is `60` for 60s.
Three advanced parameters are available `port`, `timeout`, `retries`
Usually these do not need to be changed.
If the SNMP service on the target is running on a non-standard port, it can be set with the `port` parameter.
It defaults to 161.
By using the `timeout` and `retries` parameters the timeout/error behaviour can be defined.
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`.
## Channels
The `target` thing has no fixed channels.
It can be extended with channels of type `number`, `string`, `switch`.
All channel-types have one mandatory parameter: `oid`.
It defines the OID that should be linked to this channel in dotted format (e.g. .1.2.3.4.5.6.8).
Channels can be configured in four different modes via the `mode` parameter.
Available options are `READ`, `WRITE`, `READ_WRITE` and `TRAP`.
`READ` creates a read-only channel, i.e. data is requested from the target but cannot be written.
`WRITE` creates a write-only channel, i.e. the status is never read from the target but changes to the item are written to the target.
`READ_WRITE` allows reading the status and writing it for controlling remote equipment.
`TRAP` creates a channel that ONLY reacts to traps.
It is never actively read and local changes to the item's state are not written to the target.
Using`TRAP` channels requires configuring the receiving port (see "Binding configuration").
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.
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).
`switch`-type channels send a pre-defined value if they receive `ON` or `OFF` command in `WRITE` or `READ_WRITE` mode.
In `READ`, `READ_WRITE` or `TRAP` mode they change to either `ON` or `OFF` on these values.
The parameters used for defining the values are `onvalue` and `offvalue`.
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 |
### SNMP Exception (Error) Handling
The standard behaviour if an SNMP exception occurs this is to log at `INFO` level and set the channel value to `UNDEF`.
This can be adjusted at channel level with advanced options.
The logging can be suppressed with the `doNotLogException` parameter.
If this is set to `true` any SNMP exception is not considered as faulty.
The default value is `false`.
By setting `exceptionValue` the default `UNDEF` value can be changed.
Valid values are all valid values for that channel (i.e. `ON`/`OFF` for a switch channel, a string for a string channel and a number for a number channel).
## Full Example
demo.things:
```
Thing snmp:target:router [ hostname="192.168.0.1", protocol="v2c" ] {
Channels:
Type number : inBytes [ oid=".1.3.6.1.2.1.31.1.1.1.6.2", mode="READ" ]
Type number : 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" ]
Type switch : devicePresent [ oid="1.3.6.1.2.1.2.2.1.221.4.192.168.0.1", mode="READ", datatype="UINT32", onValue="1", doNotLogException="true", exceptionValue="OFF" ]
Type switch : valueReceived [ oid="1.3.6.1.2.1.2.2.1.221.17.5", mode="READ", datatype="HEXSTRING", onValue="00 AA 11", offValue="00 00 00" ]
}
```
demo.items:
```
Number inBytes "Router bytes in [%d]" { 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" }
Switch devicePresent "Phone connected [%s]" { channel="snmp:target:router:devicePresent" }
Switch receivedValue "Received 00 AA 11 [%s]" { channel="snmp:target:router:valueReceived" }
```
demo.sitemap:
```
sitemap demo label="Main Menu"
{
Frame {
Text item=inBytes
Text item=outBytes
Text item=if4Status
Switch item=if4Command
Text item=devicePresent
Text item=receivedValue
}
}
```

View File

@@ -0,0 +1,5 @@
# Configuration for the SNMP Binding
#
# Port used for receiving traps.
# This setting defaults to 0 (disabled / not receiving traps)
#port=8162

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.snmp</artifactId>
<name>openHAB Add-ons :: Bundles :: SNMP Binding</name>
<dependencies>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.snmp4j</artifactId>
<version>2.6.3_1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.snmp-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-snmp" description="SNMP Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.snmp/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.type.ChannelTypeUID;
/**
* The {@link SnmpBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class SnmpBindingConstants {
private static final String BINDING_ID = "snmp";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_TARGET = new ThingTypeUID(BINDING_ID, "target");
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");
public static final ChannelTypeUID CHANNEL_TYPE_UID_SWITCH = new ChannelTypeUID(BINDING_ID, "switch");
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 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
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 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
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 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.openhab.binding.snmp.internal.SnmpBindingConstants.THING_TYPE_TARGET;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link SnmpHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Jan N. Klug - Initial contribution
*/
@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 final SnmpService snmpService;
@Activate
public SnmpHandlerFactory(@Reference SnmpService snmpService) {
this.snmpService = snmpService;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_TARGET.equals(thingTypeUID)) {
return new SnmpTargetHandler(thing, snmpService);
}
return null;
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 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;
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 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 java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.snmp4j.CommandResponder;
import org.snmp4j.PDU;
import org.snmp4j.Target;
import org.snmp4j.event.ResponseListener;
/**
* The {@link SnmpService} is responsible for SNMP communication
* handlers.
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public interface SnmpService {
public void addCommandResponder(CommandResponder listener);
public void removeCommandResponder(CommandResponder listener);
public void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener) throws IOException;
}

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2010-2020 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 java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.snmp.internal.config.SnmpServiceConfiguration;
import org.openhab.core.config.core.Configuration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommandResponder;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.event.ResponseListener;
import org.snmp4j.security.Priv3DES;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.transport.DefaultUdpTransportMapping;
/**
* The {@link SnmpServiceImpl} implements SnmpService
* handlers.
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.snmp", service = SnmpService.class)
public class SnmpServiceImpl implements SnmpService {
private final Logger logger = LoggerFactory.getLogger(SnmpServiceImpl.class);
private @NonNullByDefault({}) SnmpServiceConfiguration config;
private @Nullable Snmp snmp;
private @Nullable DefaultUdpTransportMapping transport;
private List<CommandResponder> listeners = new ArrayList<>();
@Activate
public SnmpServiceImpl(Map<String, Object> config) {
modified(config);
}
@Modified
protected void modified(Map<String, Object> config) {
this.config = new Configuration(config).as(SnmpServiceConfiguration.class);
try {
shutdownSnmp();
final DefaultUdpTransportMapping transport;
if (this.config.port > 0) {
transport = new DefaultUdpTransportMapping(new UdpAddress(this.config.port), true);
} else {
transport = new DefaultUdpTransportMapping();
}
SecurityProtocols.getInstance().addDefaultProtocols();
SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES());
final Snmp snmp = new Snmp(transport);
listeners.forEach(listener -> snmp.addCommandResponder(listener));
snmp.listen();
this.snmp = snmp;
this.transport = transport;
logger.debug("initialized SNMP at {}", transport.getAddress());
} catch (IOException e) {
logger.warn("could not open SNMP instance on port {}: {}", this.config.port, e.getMessage());
}
}
@Deactivate
public void deactivate() {
try {
shutdownSnmp();
} catch (IOException e) {
logger.info("could not end SNMP: {}", e.getMessage());
}
}
private void shutdownSnmp() throws IOException {
if (transport != null) {
transport.close();
transport = null;
}
if (snmp != null) {
snmp.close();
snmp = null;
}
}
@Override
public void addCommandResponder(CommandResponder listener) {
if (snmp != null) {
snmp.addCommandResponder(listener);
}
listeners.add(listener);
}
@Override
public void removeCommandResponder(CommandResponder listener) {
if (snmp != null) {
snmp.removeCommandResponder(listener);
}
listeners.remove(listener);
}
@Override
public void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener)
throws IOException {
if (snmp != null) {
snmp.send(pdu, target, userHandle, listener);
logger.trace("send {} to {}", pdu, target);
} else {
logger.warn("SNMP service not initialized, can't send {} to {}", pdu, target);
}
}
}

View File

@@ -0,0 +1,442 @@
/**
* Copyright (c) 2010-2020 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.openhab.binding.snmp.internal.SnmpBindingConstants.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
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.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.AbstractTarget;
import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.PDUv1;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.event.ResponseListener;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Counter64;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.IpAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.UnsignedInteger32;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
/**
* The {@link SnmpTargetHandler} is responsible for handling commands, which are
* sent to one of the channels or update remote channels
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class SnmpTargetHandler extends BaseThingHandler implements ResponseListener, CommandResponder {
private static final Pattern HEXSTRING_VALIDITY = Pattern.compile("([a-f0-9]{2}[ :-]?)+");
private static final Pattern HEXSTRING_EXTRACTOR = Pattern.compile("[^a-f0-9]");
private final Logger logger = LoggerFactory.getLogger(SnmpTargetHandler.class);
private @NonNullByDefault({}) SnmpTargetConfiguration config;
private final SnmpService snmpService;
private @Nullable ScheduledFuture<?> refresh;
private int timeoutCounter = 0;
private @NonNullByDefault({}) AbstractTarget target;
private @NonNullByDefault({}) String targetAddressString;
private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> readChannelSet;
private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> writeChannelSet;
private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> trapChannelSet;
public SnmpTargetHandler(Thing thing, SnmpService snmpService) {
super(thing);
this.snmpService = snmpService;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (target.getAddress() == null && !renewTargetAddress()) {
logger.info("failed to renew target address, can't process '{}' to '{}'.", command, channelUID);
return;
}
try {
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)));
snmpService.send(pdu, target, null, this);
} else if (command instanceof DecimalType || command instanceof StringType
|| command instanceof OnOffType) {
SnmpInternalChannelConfiguration channel = writeChannelSet.stream()
.filter(config -> channelUID.equals(config.channelUID)).findFirst()
.orElseThrow(() -> new IllegalArgumentException("no writable channel found"));
Variable variable;
if (command instanceof OnOffType) {
variable = OnOffType.ON.equals(command) ? channel.onValue : channel.offValue;
if (variable == null) {
logger.debug("skipping {} to {}: no value defined", command, channelUID);
return;
}
} else {
variable = convertDatatype(command, channel.datatype);
}
PDU pdu = new PDU(PDU.SET, Collections.singletonList(new VariableBinding(channel.oid, variable)));
snmpService.send(pdu, target, null, this);
}
} catch (IllegalArgumentException e) {
logger.warn("can't process command {} to {}: {}", command, channelUID, e.getMessage());
} catch (IOException e) {
logger.warn("Could not send PDU while processing command {} to {}", command, channelUID);
}
}
@Override
public void initialize() {
config = getConfigAs(SnmpTargetConfiguration.class);
generateChannelConfigs();
if (config.protocol.toInteger() == SnmpConstants.version1
|| config.protocol.toInteger() == SnmpConstants.version2c) {
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(config.community));
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");
return;
}
timeoutCounter = 0;
updateStatus(ThingStatus.UNKNOWN);
refresh = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refresh, TimeUnit.SECONDS);
}
@Override
public void dispose() {
final ScheduledFuture<?> r = refresh;
if (r != null && !r.isCancelled()) {
r.cancel(true);
}
snmpService.removeCommandResponder(this);
}
@Override
public void onResponse(@Nullable ResponseEvent event) {
if (event == null) {
return;
}
PDU response = event.getResponse();
if (response == null) {
Exception e = event.getError();
if (e == null) { // no response, no error -> request timed out
timeoutCounter++;
if (timeoutCounter > config.retries) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "request timed out");
target.setAddress(null);
}
return;
}
logger.warn("{} requested {} and got error: {}", thing.getUID(), event.getRequest(), e.getMessage());
return;
}
timeoutCounter = 0;
logger.trace("{} received {}", thing.getUID(), response);
response.getVariableBindings().forEach(variable -> {
OID oid = variable.getOid();
Variable value = variable.getVariable();
updateChannels(oid, value, readChannelSet);
});
}
@Override
public void processPdu(@Nullable CommandResponderEvent event) {
if (event == null) {
return;
}
logger.trace("{} received trap {}", thing.getUID(), event);
final PDU pdu = event.getPDU();
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)) {
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) {
trapValue = pduv1.getSpecificTrap();
}
updateChannels(oidEnterprise, new UnsignedInteger32(trapValue), trapChannelSet);
}
if ((pdu.getType() == PDU.TRAP || pdu.getType() == PDU.V1TRAP) && config.community.equals(community)
&& targetAddressString.equals(address)) {
pdu.getVariableBindings().forEach(variable -> {
OID oid = variable.getOid();
Variable value = variable.getVariable();
updateChannels(oid, value, trapChannelSet);
});
}
}
private @Nullable SnmpInternalChannelConfiguration getChannelConfigFromChannel(Channel channel) {
SnmpChannelConfiguration config = channel.getConfiguration().as(SnmpChannelConfiguration.class);
SnmpDatatype datatype;
Variable onValue = null;
Variable offValue = null;
State exceptionValue = UnDefType.UNDEF;
if (CHANNEL_TYPE_UID_NUMBER.equals(channel.getChannelTypeUID())) {
if (config.datatype == null) {
datatype = SnmpDatatype.INT32;
} else if (config.datatype == SnmpDatatype.IPADDRESS || config.datatype == SnmpDatatype.STRING) {
return null;
} else {
datatype = config.datatype;
}
if (config.exceptionValue != null) {
exceptionValue = DecimalType.valueOf(config.exceptionValue);
}
} else if (CHANNEL_TYPE_UID_STRING.equals(channel.getChannelTypeUID())) {
if (config.datatype == null) {
datatype = SnmpDatatype.STRING;
} else if (config.datatype != SnmpDatatype.IPADDRESS && config.datatype != SnmpDatatype.STRING
&& config.datatype != SnmpDatatype.HEXSTRING) {
return null;
} else {
datatype = config.datatype;
}
if (config.exceptionValue != null) {
exceptionValue = StringType.valueOf(config.exceptionValue);
}
} else if (CHANNEL_TYPE_UID_SWITCH.equals(channel.getChannelTypeUID())) {
if (config.datatype == null) {
datatype = SnmpDatatype.UINT32;
} else {
datatype = config.datatype;
}
try {
if (config.onvalue != null) {
onValue = convertDatatype(new StringType(config.onvalue), config.datatype);
}
if (config.offvalue != null) {
offValue = convertDatatype(new StringType(config.offvalue), config.datatype);
}
} catch (IllegalArgumentException e) {
logger.warn("illegal value configuration for channel {}", channel.getUID());
return null;
}
if (config.exceptionValue != null) {
exceptionValue = OnOffType.from(config.exceptionValue);
}
} else {
logger.warn("unknown channel type found for channel {}", channel.getUID());
return null;
}
return new SnmpInternalChannelConfiguration(channel.getUID(), new OID(config.oid), config.mode, datatype,
onValue, offValue, exceptionValue, config.doNotLogException);
}
private void generateChannelConfigs() {
Set<SnmpInternalChannelConfiguration> channelConfigs = Collections
.unmodifiableSet(thing.getChannels().stream().map(channel -> getChannelConfigFromChannel(channel))
.filter(Objects::nonNull).collect(Collectors.toSet()));
this.readChannelSet = channelConfigs.stream()
.filter(c -> c.mode == SnmpChannelMode.READ || c.mode == SnmpChannelMode.READ_WRITE)
.collect(Collectors.toSet());
this.writeChannelSet = channelConfigs.stream()
.filter(c -> c.mode == SnmpChannelMode.WRITE || c.mode == SnmpChannelMode.READ_WRITE)
.collect(Collectors.toSet());
this.trapChannelSet = channelConfigs.stream().filter(c -> c.mode == SnmpChannelMode.TRAP)
.collect(Collectors.toSet());
}
private void updateChannels(OID oid, Variable value, Set<SnmpInternalChannelConfiguration> channelConfigs) {
Set<SnmpInternalChannelConfiguration> updateChannelConfigs = channelConfigs.stream()
.filter(c -> c.oid.equals(oid)).collect(Collectors.toSet());
if (!updateChannelConfigs.isEmpty()) {
updateChannelConfigs.forEach(channelConfig -> {
ChannelUID channelUID = channelConfig.channelUID;
final Channel channel = thing.getChannel(channelUID);
State state;
if (channel == null) {
logger.warn("channel uid {} in channel config set but channel not found", channelUID);
return;
}
if (value.isException()) {
if (!channelConfig.doNotLogException) {
logger.info("SNMP Exception: request {} returned '{}'", oid, value);
}
state = channelConfig.exceptionValue;
} else if (CHANNEL_TYPE_UID_NUMBER.equals(channel.getChannelTypeUID())) {
try {
if (channelConfig.datatype == SnmpDatatype.FLOAT) {
state = new DecimalType(value.toString());
} else {
state = new DecimalType(value.toLong());
}
} catch (UnsupportedOperationException e) {
logger.warn("could not convert {} to number for channel {}", value, channelUID);
return;
}
} else if (CHANNEL_TYPE_UID_STRING.equals(channel.getChannelTypeUID())) {
if (channelConfig.datatype == SnmpDatatype.HEXSTRING) {
String rawString = ((OctetString) value).toHexString(' ');
state = new StringType(rawString.toLowerCase());
} else {
state = new StringType(value.toString());
}
} else if (CHANNEL_TYPE_UID_SWITCH.equals(channel.getChannelTypeUID())) {
if (value.equals(channelConfig.onValue)) {
state = OnOffType.ON;
} else if (value.equals(channelConfig.offValue)) {
state = OnOffType.OFF;
} else {
logger.debug("channel {} received unmapped value {} ", channelUID, value);
return;
}
} else {
logger.warn("channel {} has unknown ChannelTypeUID", channelUID);
return;
}
updateState(channelUID, state);
});
} else {
logger.debug("received value {} for unknown OID {}, skipping", value, oid);
}
}
private Variable convertDatatype(Command command, SnmpDatatype datatype) {
switch (datatype) {
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:
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:
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:
if (command instanceof DecimalType) {
return new OctetString(((DecimalType) command).toString());
} else if (command instanceof StringType) {
return new OctetString(((StringType) command).toString());
}
break;
case HEXSTRING:
if (command instanceof StringType) {
String commandString = ((StringType) command).toString().toLowerCase();
Matcher commandMatcher = HEXSTRING_VALIDITY.matcher(commandString);
if (commandMatcher.matches()) {
commandString = HEXSTRING_EXTRACTOR.matcher(commandString).replaceAll("");
return OctetString.fromHexStringPairs(commandString);
}
}
break;
case IPADDRESS:
if (command instanceof StringType) {
return new IpAddress(((StringType) command).toString());
}
break;
default:
}
throw new IllegalArgumentException("illegal conversion of " + command + " to " + datatype);
}
private boolean renewTargetAddress() {
try {
target.setAddress(new UdpAddress(InetAddress.getByName(config.hostname), config.port));
targetAddressString = ((UdpAddress) target.getAddress()).getInetAddress().getHostAddress();
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
return true;
} catch (UnknownHostException e) {
target.setAddress(null);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Cannot resolve target host");
return false;
}
}
private void refresh() {
if (target.getAddress() == null) {
if (!renewTargetAddress()) {
logger.info("failed to renew target address, waiting for next refresh cycle");
return;
}
}
PDU pdu = new PDU(PDU.GET,
readChannelSet.stream().map(c -> new VariableBinding(c.oid)).collect(Collectors.toList()));
if (!pdu.getVariableBindings().isEmpty()) {
try {
snmpService.send(pdu, target, null, this);
} catch (IOException e) {
logger.info("Could not send PDU", e);
}
}
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 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.config;
import org.openhab.binding.snmp.internal.SnmpChannelMode;
import org.openhab.binding.snmp.internal.SnmpDatatype;
/**
* The {@link SnmpChannelConfiguration} class contains fields mapping channel configuration parameters.
*
* @author Jan N. Klug - Initial contribution
*/
public class SnmpChannelConfiguration {
public String oid;
public SnmpChannelMode mode = SnmpChannelMode.READ;
public SnmpDatatype datatype;
public String onvalue;
public String offvalue;
public String exceptionValue;
public boolean doNotLogException = false;
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 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.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.core.thing.ChannelUID;
import org.openhab.core.types.State;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.Variable;
/**
* The {@link SnmpInternalChannelConfiguration} class contains fields mapping channel configuration parameters.
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class SnmpInternalChannelConfiguration {
public final ChannelUID channelUID;
public final OID oid;
public final SnmpChannelMode mode;
public final SnmpDatatype datatype;
public final @Nullable Variable onValue;
public final @Nullable Variable offValue;
public final State exceptionValue;
public final boolean doNotLogException;
public SnmpInternalChannelConfiguration(ChannelUID channelUID, OID oid, SnmpChannelMode mode, SnmpDatatype datatype,
@Nullable Variable onValue, @Nullable Variable offValue, State exceptionValue, boolean doNotLogException) {
this.channelUID = channelUID;
this.oid = oid;
this.mode = mode;
this.datatype = datatype;
this.onValue = onValue;
this.offValue = offValue;
this.exceptionValue = exceptionValue;
this.doNotLogException = doNotLogException;
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 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.config;
/**
* The {@link SnmpServiceConfiguration} class contains fields mapping binding configuration parameters.
*
* @author Jan N. Klug - Initial contribution
*/
public class SnmpServiceConfiguration {
public int port = 0;
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 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.config;
import org.openhab.binding.snmp.internal.SnmpProtocolVersion;
/**
* The {@link SnmpTargetConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Jan N. Klug - Initial contribution
*/
public class SnmpTargetConfiguration {
public String hostname;
public int port = 161;
public String community = "public";
public int refresh = 60;
public SnmpProtocolVersion protocol = SnmpProtocolVersion.v1;
public int timeout = 1500;
public int retries = 2;
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="snmp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>SNMP Binding</name>
<description>This is the binding for SNMP.</description>
<author>Jan N. Klug</author>
<config-description>
<parameter name="port" type="integer" min="0" max="65535">
<default>0</default>
<label>Incoming SNMP Port</label>
<description>Port for receiving traps, set to 0 to disable.</description>
</parameter>
</config-description>
</binding:binding>

View File

@@ -0,0 +1,213 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="snmp"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="target" extensible="number,string,switch">
<label>SNMP 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>
<!-- optional -->
<parameter name="protocol" type="text">
<label>SNMP Version</label>
<options>
<option value="v1">V1</option>
<option value="v2c">V2c</option>
</options>
<limitToOptions>true</limitToOptions>
<default>v1</default>
</parameter>
<parameter name="community" type="text">
<label>SNMP Community</label>
<default>public</default>
</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">
<item-type>Number</item-type>
<label>Number</label>
<config-description>
<parameter name="oid" type="text" required="true">
<label>OID</label>
<description>OID in dotted format (eg. .1.3.6.1.4.1.6574.3.1.1.3.0)</description>
</parameter>
<parameter name="mode" type="text">
<label>Mode</label>
<description>the mode of this channel</description>
<options>
<option value="READ">Read</option>
<option value="WRITE">Write</option>
<option value="READ_WRITE">Read/Write</option>
<option value="TRAP">Trap</option>
</options>
<default>READ</default>
<limitToOptions>true</limitToOptions>
</parameter>
<parameter name="datatype" type="text">
<label>Datatype</label>
<description>Content data type</description>
<options>
<option value="UINT32">Unsigned Integer (32 bit)</option>
<option value="INT32">Integer (32 bit)</option>
<option value="COUNTER64">Counter (64 bit)</option>
<option value="FLOAT">Float</option>
</options>
<default>UINT32</default>
<limitToOptions>true</limitToOptions>
</parameter>
<parameter name="doNotLogException" type="boolean">
<label>Don't Log Exception</label>
<description>If enabled, ignore faulty values/exceptions in this channel</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
<parameter name="exceptionValue" type="integer">
<label>Exception Value</label>
<description>Value to send if an SNMP exception occurs (default: UNDEF)</description>
<advanced>true</advanced>
</parameter>
</config-description>
</channel-type>
<channel-type id="string">
<item-type>String</item-type>
<label>String</label>
<config-description>
<parameter name="oid" type="text" required="true">
<label>OID</label>
<description>OID in dotted format (eg. .1.3.6.1.4.1.6574.3.1.1.3.0)</description>
</parameter>
<parameter name="mode" type="text">
<label>Mode</label>
<description>the mode of this channel</description>
<options>
<option value="READ">Read</option>
<option value="WRITE">Write</option>
<option value="READ_WRITE">Read/Write</option>
<option value="TRAP">Trap</option>
</options>
<default>READ</default>
<limitToOptions>true</limitToOptions>
</parameter>
<parameter name="datatype" type="text">
<label>Datatype</label>
<description>Content data type</description>
<options>
<option value="STRING">String</option>
<option value="HEXSTRING">Hex-String</option>
<option value="IPADDRESS">IP Address</option>
</options>
<default>STRING</default>
<limitToOptions>true</limitToOptions>
</parameter>
<parameter name="doNotLogException" type="boolean">
<label>Don't Log Exception</label>
<description>If enabled, ignore faulty values/exceptions in this channel</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
<parameter name="exceptionValue" type="text">
<label>Exception Value</label>
<description>Value to send if an SNMP exception occurs (default: UNDEF)</description>
<advanced>true</advanced>
</parameter>
</config-description>
</channel-type>
<channel-type id="switch">
<item-type>Switch</item-type>
<label>Switch</label>
<config-description>
<parameter name="oid" type="text" required="true">
<label>OID</label>
<description>OID in dotted format (eg. .1.3.6.1.4.1.6574.3.1.1.3.0)</description>
</parameter>
<parameter name="mode" type="text">
<label>Mode</label>
<description>the mode of this channel</description>
<options>
<option value="READ">Read</option>
<option value="WRITE">Write</option>
<option value="READ_WRITE">Read/Write</option>
<option value="TRAP">Trap</option>
</options>
<default>READ</default>
<limitToOptions>true</limitToOptions>
</parameter>
<parameter name="datatype" type="text">
<label>Datatype</label>
<description>Content data type</description>
<options>
<option value="UINT32">Unsigned Integer (32 bit)</option>
<option value="INT32">Integer (32 bit)</option>
<option value="COUNTER64">Counter (64 bit)</option>
<option value="STRING">String</option>
<option value="HEXSTRING">Hex-String</option>
<option value="IPADDRESS">IP Address</option>
</options>
<default>UINT32</default>
<limitToOptions>true</limitToOptions>
</parameter>
<parameter name="onvalue" type="text">
<label>On-Value</label>
<description>Value that equals ON</description>
</parameter>
<parameter name="offvalue" type="text">
<label>Off-Value</label>
<description>Value that equals OFF</description>
</parameter>
<parameter name="doNotLogException" type="boolean">
<label>Don't Log Exception</label>
<description>If enabled, faulty values/exceptions will not be logged in this channel</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
<parameter name="exceptionValue" type="text">
<label>Exception Value</label>
<description>Value to send if an SNMP exception occurs (ON, OFF, default: UNDEF)</description>
<options>
<option value="ON">ON</option>
<option value="OFF">OFF</option>
</options>
<limitToOptions>true</limitToOptions>
<advanced>true</advanced>
</parameter>
</config-description>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,211 @@
/**
* Copyright (c) 2010-2020 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.Assert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.openhab.binding.snmp.internal.SnmpBindingConstants.THING_TYPE_TARGET;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.StringType;
import org.openhab.core.test.java.JavaTest;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.snmp4j.PDU;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
/**
* Tests cases for {@link SnmpTargetHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
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_OID = "1.2.3.4";
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 Thing thing;
protected SnmpTargetHandler thingHandler;
protected VariableBinding handleCommandSwitchChannel(SnmpDatatype datatype, Command command, String onValue,
String offValue, boolean refresh) throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_SWITCH, SnmpChannelMode.WRITE, datatype, onValue, offValue);
thingHandler.handleCommand(CHANNEL_UID, command);
if (refresh) {
ArgumentCaptor<PDU> pduCaptor = ArgumentCaptor.forClass(PDU.class);
verify(snmpService, times(1)).send(pduCaptor.capture(), any(), eq(null), eq(thingHandler));
return pduCaptor.getValue().getVariableBindings().stream().findFirst().orElse(null);
} else {
verify(snmpService, never()).send(any(), any(), eq(null), eq(thingHandler));
return null;
}
}
protected VariableBinding handleCommandNumberStringChannel(ChannelTypeUID channelTypeUID, SnmpDatatype datatype,
Command command, boolean refresh) throws IOException {
setup(channelTypeUID, SnmpChannelMode.WRITE, datatype);
thingHandler.handleCommand(CHANNEL_UID, command);
if (refresh) {
ArgumentCaptor<PDU> pduCaptor = ArgumentCaptor.forClass(PDU.class);
verify(snmpService, times(1)).send(pduCaptor.capture(), any(), eq(null), eq(thingHandler));
return pduCaptor.getValue().getVariableBindings().stream().findFirst().orElse(null);
} else {
verify(snmpService, never()).send(any(), any(), eq(null), eq(thingHandler));
return null;
}
}
protected void onResponseNumberStringChannel(SnmpChannelMode channelMode, boolean refresh) {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_STRING, channelMode);
PDU responsePDU = new PDU(PDU.RESPONSE,
Collections.singletonList(new VariableBinding(new OID(TEST_OID), new OctetString(TEST_STRING))));
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
thingHandler.onResponse(event);
if (refresh) {
verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), eq(new StringType(TEST_STRING)));
} else {
verify(thingHandlerCallback, never()).stateUpdated(any(), any());
}
}
protected 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,
Collections.singletonList(new VariableBinding(new OID(TEST_OID), value)));
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
thingHandler.onResponse(event);
if (refresh) {
ArgumentCaptor<State> stateCaptor = ArgumentCaptor.forClass(State.class);
verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), stateCaptor.capture());
return stateCaptor.getValue();
} else {
verify(thingHandlerCallback, never()).stateUpdated(any(), any());
return null;
}
}
protected void refresh(SnmpChannelMode channelMode, boolean refresh) throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_STRING, channelMode);
waitForAssert(() -> assertEquals(ThingStatus.ONLINE, thingHandler.getThing().getStatusInfo().getStatus()));
verify(snmpService).addCommandResponder(any());
if (refresh) {
ArgumentCaptor<PDU> pduCaptor = ArgumentCaptor.forClass(PDU.class);
verify(snmpService, atLeast(1)).send(pduCaptor.capture(), any(), eq(null), eq(thingHandler));
Vector<? extends VariableBinding> variables = pduCaptor.getValue().getVariableBindings();
assertTrue(variables.stream().filter(v -> v.getOid().toDottedString().equals(TEST_OID)).findFirst()
.isPresent());
} else {
verify(snmpService, never()).send(any(), any(), eq(null), eq(thingHandler));
}
}
protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode) {
setup(channelTypeUID, channelMode, null);
}
protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, SnmpDatatype datatype) {
setup(channelTypeUID, channelMode, datatype, null, null);
}
protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, SnmpDatatype datatype,
String onValue, String offValue) {
setup(channelTypeUID, channelMode, datatype, onValue, offValue, null);
}
protected void setup(ChannelTypeUID channelTypeUID, SnmpChannelMode channelMode, SnmpDatatype datatype,
String onValue, String offValue, String exceptionValue) {
Map<String, Object> channelConfig = new HashMap<>();
Map<String, Object> thingConfig = new HashMap<>();
MockitoAnnotations.initMocks(this);
thingConfig.put("hostname", "localhost");
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);
}
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);
thingHandler.getThing().setHandler(thingHandler);
thingHandler.setCallback(thingHandlerCallback);
doAnswer(answer -> {
((Thing) answer.getArgument(0)).setStatusInfo(answer.getArgument(1));
return null;
}).when(thingHandlerCallback).statusUpdated(any(), any());
thingHandler.initialize();
waitForAssert(() -> assertEquals(ThingStatus.ONLINE, thingHandler.getThing().getStatusInfo().getStatus()));
}
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2010-2020 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.Assert.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.Collections;
import org.junit.Test;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.snmp4j.PDU;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.smi.Counter64;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UnsignedInteger32;
import org.snmp4j.smi.VariableBinding;
/**
* Tests cases for {@link SnmpTargetHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
public class SnmpTargetHandlerTest extends AbstractSnmpTargetHandlerTest {
@Test
public void testChannelsProperlyRefreshing() throws IOException {
refresh(SnmpChannelMode.READ, true);
refresh(SnmpChannelMode.READ_WRITE, true);
refresh(SnmpChannelMode.WRITE, false);
refresh(SnmpChannelMode.TRAP, false);
}
@Test
public void testChannelsProperlyUpdate() throws IOException {
onResponseNumberStringChannel(SnmpChannelMode.READ, true);
onResponseNumberStringChannel(SnmpChannelMode.READ_WRITE, true);
onResponseNumberStringChannel(SnmpChannelMode.WRITE, false);
onResponseNumberStringChannel(SnmpChannelMode.TRAP, false);
assertEquals(OnOffType.ON, onResponseSwitchChannel(SnmpChannelMode.READ, SnmpDatatype.STRING, "on", "off",
new OctetString("on"), true));
assertEquals(OnOffType.OFF, onResponseSwitchChannel(SnmpChannelMode.READ_WRITE, SnmpDatatype.INT32, "1", "2",
new Integer32(2), true));
assertNull(onResponseSwitchChannel(SnmpChannelMode.WRITE, SnmpDatatype.STRING, "on", "off",
new OctetString("on"), false));
assertNull(
onResponseSwitchChannel(SnmpChannelMode.TRAP, SnmpDatatype.INT32, "1", "2", new Integer32(2), false));
}
@Test
public void testCommandsAreProperlyHandledByNumberChannel() throws IOException {
VariableBinding variable;
variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpDatatype.INT32,
new DecimalType(-5), true);
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);
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);
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);
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.INT32,
new StringType(TEST_STRING), false);
assertNull(variable);
}
@Test
public void testNumberChannelsProperlyUpdatingFloatValue() throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_NUMBER, SnmpChannelMode.READ, SnmpDatatype.FLOAT);
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 DecimalType("12.4")));
}
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2010-2020 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.Assert.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.Collections;
import org.junit.Test;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.snmp4j.PDU;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.smi.IpAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
/**
* Tests cases for {@link SnmpTargetHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
public class StringChannelTest extends AbstractSnmpTargetHandlerTest {
@Test
public void testCommandsAreProperlyHandledByStringChannel() throws IOException {
VariableBinding variable;
variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_STRING, SnmpDatatype.STRING,
new StringType(TEST_STRING), true);
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.IPADDRESS, new StringType(TEST_STRING), false);
assertNull(variable);
variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_STRING,
SnmpDatatype.IPADDRESS, new DecimalType(-5), false);
assertNull(variable);
variable = handleCommandNumberStringChannel(SnmpBindingConstants.CHANNEL_TYPE_UID_STRING,
SnmpDatatype.HEXSTRING, new StringType("AA bf 11"), true);
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);
assertEquals(new OID(TEST_OID), variable.getOid());
assertTrue(variable.getVariable() instanceof IpAddress);
assertEquals(TEST_ADDRESS, ((IpAddress) variable.getVariable()).toString());
}
@Test
public void testStringChannelsProperlyUpdatingOnHexString() throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_STRING, SnmpChannelMode.READ, SnmpDatatype.HEXSTRING);
PDU responsePDU = new PDU(PDU.RESPONSE, Collections
.singletonList(new VariableBinding(new OID(TEST_OID), OctetString.fromHexStringPairs("aa11bb"))));
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
thingHandler.onResponse(event);
verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), eq(new StringType("aa 11 bb")));
}
}

View File

@@ -0,0 +1,120 @@
/**
* Copyright (c) 2010-2020 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.Assert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.Collections;
import org.junit.Test;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.UnDefType;
import org.snmp4j.PDU;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.smi.Counter64;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.Null;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
/**
* Tests cases for {@link SnmpTargetHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
public class SwitchChannelTest extends AbstractSnmpTargetHandlerTest {
@Test
public void testCommandsAreProperlyHandledBySwitchChannel() throws IOException {
VariableBinding variable;
variable = handleCommandSwitchChannel(SnmpDatatype.STRING, OnOffType.ON, "on", "off", true);
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);
assertEquals(new OID(TEST_OID), variable.getOid());
assertTrue(variable.getVariable() instanceof OctetString);
assertEquals("off", ((OctetString) variable.getVariable()).toString());
variable = handleCommandSwitchChannel(SnmpDatatype.STRING, OnOffType.OFF, "on", null, false);
assertNull(variable);
}
@Test
public void testSwitchChannelsProperlyUpdatingOnValue() throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_SWITCH, SnmpChannelMode.READ, SnmpDatatype.STRING, "on", "off");
PDU responsePDU = new PDU(PDU.RESPONSE,
Collections.singletonList(new VariableBinding(new OID(TEST_OID), new OctetString("on"))));
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
thingHandler.onResponse(event);
verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), eq(OnOffType.ON));
}
@Test
public void testSwitchChannelsProperlyUpdatingOffValue() throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_SWITCH, SnmpChannelMode.READ, SnmpDatatype.INT32, "0", "3");
PDU responsePDU = new PDU(PDU.RESPONSE,
Collections.singletonList(new VariableBinding(new OID(TEST_OID), new Integer32(3))));
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
thingHandler.onResponse(event);
verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), eq(OnOffType.OFF));
}
@Test
public void testSwitchChannelsProperlyUpdatingHexValue() throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_SWITCH, SnmpChannelMode.READ, SnmpDatatype.HEXSTRING, "AA bb 11",
"cc ba 1d");
PDU responsePDU = new PDU(PDU.RESPONSE, Collections
.singletonList(new VariableBinding(new OID(TEST_OID), OctetString.fromHexStringPairs("aabb11"))));
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
thingHandler.onResponse(event);
verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), eq(OnOffType.ON));
}
@Test
public void testSwitchChannelsIgnoresArbitraryValue() throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_SWITCH, SnmpChannelMode.READ, SnmpDatatype.COUNTER64, "0", "12223");
PDU responsePDU = new PDU(PDU.RESPONSE,
Collections.singletonList(new VariableBinding(new OID(TEST_OID), new Counter64(17))));
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
thingHandler.onResponse(event);
verify(thingHandlerCallback, never()).stateUpdated(eq(CHANNEL_UID), any());
}
@Test
public void testSwitchChannelSendsUndefExceptionValue() throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_SWITCH, SnmpChannelMode.READ, SnmpDatatype.COUNTER64, "0", "12223");
PDU responsePDU = new PDU(PDU.RESPONSE,
Collections.singletonList(new VariableBinding(new OID(TEST_OID), Null.noSuchInstance)));
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
thingHandler.onResponse(event);
verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), eq(UnDefType.UNDEF));
}
@Test
public void testSwitchChannelSendsConfiguredExceptionValue() throws IOException {
setup(SnmpBindingConstants.CHANNEL_TYPE_UID_SWITCH, SnmpChannelMode.READ, SnmpDatatype.COUNTER64, "0", "12223",
"OFF");
PDU responsePDU = new PDU(PDU.RESPONSE,
Collections.singletonList(new VariableBinding(new OID(TEST_OID), Null.noSuchInstance)));
ResponseEvent event = new ResponseEvent("test", null, null, responsePDU, null);
thingHandler.onResponse(event);
verify(thingHandlerCallback, atLeast(1)).stateUpdated(eq(CHANNEL_UID), eq(OnOffType.OFF));
}
}