DNP3bean User Guide

Demo

The demo library supports all features of the full commercial variant but it can only be used for 10 minutes once it connects for the first time. To use the demo library in your application include the following dependency hosted on maven central:

maven:

<dependency>
    <groupId>com.beanit</groupId>
    <artifactId>dnp3bean-demo</artifactId>
    <version>0.5.0</version>
</dependency>

gradle:

implementation group: 'com.beanit', name: 'dnp3bean-demo', version: '0.5.0'

Console App

The distribution contains two console (i.e. terminal) apps that can be used for testing purposes: dnp3bean-console-master and dnp3bean-console-outstation. Please use the respective .bat files when using Windows.

When running dnp3bean-console-master at least a host needs to be provided, e.g.:

> dnp3bean-console-master localhost

When running dnp3bean-console-outstation you need to provide a configuration file to configure the data points of that outstation, e.g.:

> dnp3bean-console-outstation outstation-cfg.yml

A sample configuration file can be found in the bin/ folder of the distribution. The sample configuration also serves as documentation for the syntax of the configuration file.

Master API

To connect to a DNP3 outstation you need to create an instance of class Master. It represents an association of the master to an outstation. At any point in time the association may be active (i.e. a TCP connection exists) or inactive. When inactive, the association attempts to automatically reactivate itself periodically.

An instance of Master is created using Master.TcpBuilder. A master is an AutoCloseable and can therefore be used in a try-with-resources block:

try (Master master = Master.newTcpBuilder().outstationHost("localhost").build()) {
  //requests
}

By default a master uses the initiating endpoint mode in which it connects to that outstation. But a master can also be configured to used listening or dual end point modes. When using the master in listening mode you may want to resuse the builder so that multiple masters can listen on the same port as explained below.

Requests are sent using respective instance methods of Master, e.g. read(ReadRequest request). Many request methods such as read take a request object as argument. These request objects are also created using a builder.

Reading Data

A read request can consist of one or more subrequests. For example the so called intergrity poll consists of four subrequests for classes 0 to 3. Such a request can be built using a builder:

ReadRequest readRequest = ReadRequest.newBuilder().readAll(CLASS_1)
    .readAll(CLASS_2).readAll(CLASS_3).readAll(CLASS_0).build();

Because the integrity poll is such a commonly used request there exists a shortcut:

ReadRequest readRequest = ReadRequest.newBuilder().integrityPoll().build();

A request consisting of multiple range reads can be constructed like this:

ReadRequest request = ReadRequest.newBuilder()
    .readRange(BINARY_INPUT_ANY, 0, 0)
    .readRange(BINARY_INPUT_ANY, 2, 3)
    .build();

A read response consists of the data points read:

ReadResponse response = master.read(readRequest);
List<DataPoint> dataPoints = response.dataPoints();

Each data point consists of:

  • an address of type DpAddress that uniquely identifies the data point
  • a value of type Dnp3Value
  • optionally a set of flags
  • optionally a timestamp

To get the value of a data point its value of type Dnp3Value needs to be cast to a more concrete type. Check the Javadoc for all subclass of Dnp3Value. Often times you will know what type to cast to based on the address of the received data point, e.g.:

if (dataPoint.address().group() == StaticGroup.BINARY_INPUT) {
  boolean val = ((Binary) dnp3Value).value();
  ...
}

At other times you might want to do an instanceof check:

Dnp3Value dnp3Value = dataPoint.value();
if (dnp3Value instanceof AnalogValue) {
  int val = ((AnalogValue) dnp3Value).intValue();
}

Note that the ReadResponse returned by the read method does not contain any events returned by the outstation. Instead all events are configured EventListener.

Events

An outstation can send events either in response to a read request or in so called unsoclicited responses. In both cases the events are given to the configured EventListener.

Outstation API

The outstation API consists of two main classes: Outstation and DataSource.

The DataSource contains all data points of the outstation. Multiple outstations can share a single DataSource. Thus before constructing an Outstation you need to create a DataSource and configure it with all data points it contains.

    DataSource dataSource =
        DataSource.newBuilder()
            .analogInput(
                0,
                CLASS_1,
                DATA_CHANGE,
                ANALOG_INPUT_INT32_WITH_FLAGS,
                ANALOG_INPUT_EVENT_INT32_WITH_TIME)
            .binaryInput(0, CLASS_1, DATA_UPDATE, BINARY_INPUT_EVENT_WITH_ABS_TIME)
            .binaryInput(1, CLASS_1, DATA_UPDATE, BINARY_INPUT_EVENT_WITH_ABS_TIME)
            .binaryInput(2, CLASS_1, DATA_UPDATE, BINARY_INPUT_EVENT_WITH_ABS_TIME)
            .binaryInput(3, CLASS_1, DATA_UPDATE, BINARY_INPUT_EVENT_WITH_ABS_TIME)
            .build();

Next you will need to create an EventBuffer instance. The DNP3 standard does not define how events shall be buffered. DNP3bean provides a QueueEventBuffer that can be easily and flexibly constructed:

EventBuffer eventBuffer = QueueEventBuffer.newBuilder().allGroupsQueue(100).build();

Next you can create the Outstation using a builder. At a minimum you need to configure the DataSource and the EventBuffer that you created in the previous steps. Calling the builder’s build method will create and run an instance of Outstation. To stop the outstation call it’s close() method or use it in a try-with-resources block.

try (Outstation outstation =
        Outstation.newTcpBuilder().dataSource(dataSource).eventBuffer(eventBuffer).build()) {
}

Setting Data

The Outstation’s current data is managed by the DataSource incstance you created. Thus you use the DataSource to update data so that it can be read by master’s that connect.

dataSource.update(DataPoint.of(StaticGroup.BINARY_INPUT, 0, Binary.valueOf(value)));

Common Concepts of Master and Outstation API

Reusing the Builder

Note that you can reuse an outstation’s or master’s builder to create more outstations or masters. Thus after calling build() you can reconfigure the builder and call build() a second time. Masters or outstations created using the same builder can share resources (such as thread pools). Reusing the builder is required if multiple outstations or masters shall listen on the same port for connections.

Connections

Usually a master (i.e. client) connects to an outstation (i.e. server). In this case the master is the initiating endpoint and the outstation is the listening endpoint. DNP3 is special compared to other protocols because it also allows the outstation to connect to the master. In this case the outstation is the initiating endpoint and the master the listening endpoint. By default the listening endpoint accepts connections from any IP. But you can restrict what IPs can connect by setting a whitelist.

As required by the DNP3 standard a single outstation only accepts a single connection by a master. But it is easy to create multiple outstations that share a single DataSource and optionally listen on the same port. This way multiple masters can connect to the same data. Outstations listening on the same interface and port CANNOT have overlapping master whitelists because incoming connections are assigned to outstations based on the whitelist. Check the Javadoc for details.