DNP3

Pull data from DNP3 outstations into AnyLog as master over TCP or TLS using run plc client.


AnyLog can act as a DNP3 master over TCP or TLS (using hostname and port, default 20000). Data is read on a schedule and streamed into your local operator database as JSON, using the same run plc client pattern as Modbus, OPC-UA, and EtherNet/IP.

For a production-style deployment of a DNP3 connection from a standing script (rather than one-off CLI commands), see Deploying a DNP3 Connector via Script. For reusing a single point map across multiple outstations instead of repeating it inline, see DNP3 Mapping Policies.


Prerequisites

Requirement Notes
opendnp3 (yadnp3) Must be installed in the AnyLog runtime environment.

Declare the target DBMS before streaming, for example:

connect dbms new_company where type = sqlite

Connection and map

DNP3 uses hostname and port. master_id and outstation_id are the DNP3 link addresses (defaults: master 1, outstation 10).

map is a JSON array of points. Each object must include:

Key Required Meaning
name yes Column / logical label
index yes Point index on the outstation
type one of type or group+variation Point class (see table below)
group, variation alternative to type Raw DNP3 group/variation

Supported type values (case-insensitive; spaces/underscores ignored):

type DNP3 group / variation
Analog 30 / 6
Binary 1 / 2
BinaryOutputStatus 10 / 2
AnalogOutputStatus or AnalogOutput 40 / 4
Counter 20 / 6
DoubleBit or DoubleBitBinary 3 / 2

map can be written inline, as shown throughout this page, or declared once as a reusable policy on the blockchain and referenced across multiple connections — see DNP3 Mapping Policies.

Optional TLS (all three PEM paths required when enable_tls = true):

Keyword Meaning
enable_tls Use TLS instead of plain TCP
tls_ca CA certificate (PEM file path)
tls_cert Client certificate (PEM file path)
tls_key Client private key (PEM file path)

One-shot read

<get plc values where type = dnp3 and
    hostname = 127.0.0.1 and
    port = 20001 and
    master_id = 1 and
    outstation_id = 10 and
    map = [{"name":"analog_0","type":"Analog","index":0},
           {"name":"binary_0","type":"Binary","index":0},
           {"name":"counter_0","type":"Counter","index":0},
           {"name":"doublebit_0","type":"DoubleBit","index":0}]
>

Alias: get dnp3 values (same keywords).

With TLS (see DNP3 TLS Test Certificates for generating a chain to use here, and DNP3 Out Station Testing below for running a matching test outstation):

<get dnp3 values where
    hostname = 127.0.0.1 and
    port = 20001 and
    master_id = 1 and
    outstation_id = 10 and
    enable_tls = true and
    tls_ca = /path/to/your/working/dir/anylogDNP3ca.cert and
    tls_cert = /path/to/your/working/dir/master1.cert and
    tls_key = /path/to/your/working/dir/master1.key and
    map = [{"name":"analog_0","type":"Analog","index":0}]
>

Continuous ingest — wide table (default)

With table = ... and dbms, all points from map land in one table. Each poll inserts one row; every object in map is one column: the map name is the column name, and that column stores the value read for that point.

<run plc client where type = dnp3 and
    hostname = 127.0.0.1 and
    port = 20001 and
    master_id = 1 and
    outstation_id = 10 and
    frequency = 20 and
    name = dnp3_rtu and
    dbms = new_company and
    table = substation and
    map = [{"name":"analog_0","type":"Analog","index":0}]
>

Continuous ingest — dynamic tables (dynamic = true)

Omit table and omit namespace for plain dynamic ingest. Each object in map is written to its own table. The table name is derived from the client name and the map name (for example, dnp3_rtu_analog_0 when name = dnp3_rtu and the map entry’s name is analog_0). Each row includes timestamp, tag, and value.

<run plc client where type = dnp3 and
    hostname = 127.0.0.1 and
    port = 20001 and
    master_id = 1 and
    outstation_id = 10 and
    frequency = 20 and
    name = dnp3_dyn and
    dbms = new_company and
    dynamic = true and
    map = [{"name":"analog_0","type":"Analog","index":0}]
>

Dynamic ingest with UNS (namespace + master_node)

With dynamic = true, you can add a Unified Namespace path and a master node so DNP3 ingest is registered in the UNS alongside your policies and DBMS. namespace requires master_node = [ip:port] for policy updates.

run plc client where type = dnp3 and
    hostname = 127.0.0.1 and
    port = 20001 and
    master_id = 1 and
    outstation_id = 10 and
    frequency = 20 and
    name = dnp3_uns and
    dbms = new_company and
    dynamic = true and
    master_node = 192.168.1.88:32048 and
    namespace = FA9/MID9/DEVICE9 and
    map = [{"name":"analog_0","type":"Analog","index":0}]

Table names follow the same pattern as plain dynamic = true (client name plus map name, e.g. dnp3_uns_analog_0 for the example above). Under UNS, the read value is usually stored in a column named like the tag—the map name (here analog_0), not a generic value column.

With namespace, table and column layout follow UNS policies. namespace and master_node drive how tables are registered in the UNS. See Unified Namespace for background.

For a full example combining this with a scripted deployment and a reusable mapping policy (rather than an inline map array), see Deploying a DNP3 Connector via Script.


DNP3 Out Station Testing

The opendnp3 library includes a demo outstation for lab tests. Build it with demos enabled (TLS optional):

git clone --recursive -b release-2.x https://github.com/dnp3/opendnp3.git
cd opendnp3
mkdir build && cd build
cmake -DDNP3_DEMO=ON -DDNP3_TLS=ON ..    # omit -DDNP3_TLS=ON for TCP-only
make -j

See the OpenDNP3 CMake guide and TLS support (OpenSSL ≥ 1.1.1 required for TLS).

Plain TCP outstation

From the opendnp3 build directory:

cd ~/opendnp3/build
./outstation-demo

The demo listens on any IP address, port 20001, outstation link id 10, and expects master link id 1. Use those values in AnyLog:

<get dnp3 values where type = dnp3 and
    hostname = 127.0.0.1 and
    port = 20001 and
    master_id = 1 and
    outstation_id = 10 and
    map = [{"name":"analog_0","type":"Analog","index":0},
           {"name":"binary_0","type":"Binary","index":0},
           {"name":"counter_0","type":"Counter","index":0},
           {"name":"doublebit_0","type":"DoubleBit","index":0}]
>

Once started, the demo logs traffic and waits for input to send unsolicited measurement changes:

Enter one or more measurement changes then press <enter>
c = counter, b = binary, d = doublebit, a = analog, o = octet string, 'quit' = exit

TLS outstation

Generate a test CA chain first — see DNP3 TLS Test Certificates for the full script and instructions. Run it in a working directory of your own, outside this repository:

mkdir -p ~/dnp3-tls-test && cd ~/dnp3-tls-test
bash create_certificates.sh
Side CA (peer) Certificate Private key
AnyLog master anylogDNP3ca.cert master1.cert master1.key
Outstation anylogDNP3ca.cert outstation1.cert outstation1.key

From the opendnp3 build directory, start the TLS demo with three PEM paths (CA, outstation certificate, outstation private key):

cd ~/opendnp3/build
./outstation-tls-demo \
  ~/dnp3-tls-test/anylogDNP3ca.cert \
  ~/dnp3-tls-test/outstation1.cert \
  ~/dnp3-tls-test/outstation1.key

Same link ids and port as plain TCP (master_id = 1, outstation_id = 10, port 20001). AnyLog master uses the master certificate files from the same CA chain.

Example AnyLog one-shot read:

<get dnp3 values where type = dnp3 and
    hostname = 127.0.0.1 and
    port = 20001 and
    master_id = 1 and
    outstation_id = 10 and
    enable_tls = true and
    tls_ca = ~/dnp3-tls-test/anylogDNP3ca.cert and
    tls_cert = ~/dnp3-tls-test/master1.cert and
    tls_key = ~/dnp3-tls-test/master1.key and
    map = [{"name":"analog_0","type":"Analog","index":0}]
>

More detail, including the full generation script: DNP3 TLS Test Certificates.

Third-party simulator

Another option is a commercial DNP3 outstation simulator, for example the FreyrSCADA DNP3 development bundle. After download, DNPOutstationSimulator.exe under the Simulator folder can be installed on Windows (32-bit).


Command keywords (summary)

Keyword Required / notes
type dnp3
hostname, port DNP3 TCP/TLS target (default port 20000)
master_id Master link address (default 1)
outstation_id Outstation link address (default 10)
frequency Poll interval (seconds)
name Unique client name
dbms Target DBMS
table Wide-table ingest; omit with dynamic = true
dynamic true for per-map tables or UNS
map JSON array of points, inline or resolved from a mapping policy
namespace UNS path (DNP3 + dynamic = true only)
master_node Required when namespace is set
enable_tls, tls_ca, tls_cert, tls_key Optional TLS (all three PEM paths required)