DNP3
Synopsis
Director reads one DNP3 outstation (IEEE 1815): a substation's RTU, a protection relay or another IED, a recloser's controller, or a pumping station's or a pipeline's RTU. Director is a master to it, as the utility's SCADA is. It connects over TCP, to the outstation itself (port 20000) or to the terminal server in front of its serial line, asks for the outstation's data by class, takes what the outstation sends unasked, and writes it as ECS records. Nothing is installed on the outstation.
It never operates anything. The device sends a read of class data, a confirmation and, only with unsolicited: true, the request that turns unsolicited responses on. It has no request to operate an output, write a value, freeze a counter, restart the outstation, or set its clock (see What It Sends). So the outstation's restart indication is never cleared, and its clock is never set.
The device needs a master session of its own at the outstation. An outstation keeps its events for each master apart, and deletes an event once that master confirms it. Give Director its own master address in the outstation's configuration (master_address), with event buffers of its own. A device that shared the session of the utility's SCADA would take the events the SCADA has not read yet, and the SCADA the ones the device has not: whichever polled first would have them, and the other would never see them. This is why both link addresses are required and have no default. An outstation that has one session only, which its SCADA uses, is read through a data concentrator that gives Director a session of its own.
One device, one outstation. Monitoring twelve outstations takes twelve devices. The device dials the outstation and binds no port, so any number of DNP3 devices, on one Director or several, can name port 20000. One Director owns each device: two would be two masters in one session, each taking events the other never sees.
What to write is declared by datasets. An outstation's points have a type and an index, and no name a master can read: that analog input 6 is the bus voltage is in the utility's point list. So a point dataset is your own, a table of the points whose present values it writes, each named from the point list. An event dataset writes every event the outstation reports, with the outstation's own time of it, in the order delivered: its sequence of events.
The device runs on Director only; no agent runs it.
The DNP3 device needs Director 2.4.1 or later. With no Director or cluster at that version selected, the wizard shows DNP3 devices are unavailable on directors or clusters below version 2.4.1 and the device cannot be created.
Schema
- id: <numeric>
name: <string>
description: <string>
type: dnp3
tags: <string[]>
pipelines: <pipeline[]>
status: <boolean>
properties:
address: <string>
port: <numeric>
master_address: <numeric>
outstation_address: <numeric>
integrity_interval: <numeric>
class1_interval: <numeric>
class2_interval: <numeric>
class3_interval: <numeric>
unsolicited: <boolean>
timeout: <numeric>
keep_alive: <numeric>
max_reconnect_interval: <numeric>
definitions: <definition[]>
Configuration
The following fields are used to define the device:
Device
| Field | Required | Default | Description |
|---|---|---|---|
id | Y | - | Unique numeric identifier |
name | Y | - | Device name |
description | N | - | Optional description |
type | Y | - | Must be dnp3 |
tags | N | - | Optional tags |
pipelines | N | - | Optional pre-processor pipelines |
status | N | true | Enable/disable the device |
Connection
| Field | Required | Default | Description |
|---|---|---|---|
address | Y | - | Host name or IP address of the outstation, or of the terminal server its serial line is behind. An IPv6 address can be written as it is or in brackets. A wildcard address (0.0.0.0, ::) is refused, and so is a port written into the address: the port is its own field. |
port | N | 20000 | From 1 to 65535. DNP3's port, which outstations listen on over TCP; a terminal server's is whatever port it gives the line. |
master_address | Y | - | From 0 to 65519. The link address the outstation knows Director by, which names Director's own session there. It has no default: any default would be the address some outstation's SCADA already has. |
outstation_address | Y | - | From 0 to 65519. The outstation's own link address. It cannot be the same as master_address. |
integrity_interval | N | 3600 | Seconds between two integrity polls, which read the events of every class and every point's present value, from 1 to 86400. 0 makes one only when the device connects and when the outstation gives cause for one (see What the Outstation Says). |
class1_interval, class2_interval, class3_interval | N | 5 | Seconds between two polls of each class of events, from 1 to 86400 each. 0 polls the class on no interval: it is then read at every integrity poll, when the outstation says it holds events of it, and from the unsolicited responses. |
unsolicited | N | false | false asks the outstation for nothing, and still takes and confirms the unsolicited responses it sends of its own accord. true asks it, once the first integrity poll of a connection is answered, to report the events of the three classes as they happen. |
timeout | N | 5 | Seconds the connect, and each fragment of an answer, are waited for, from 1 to 300. An outstation behind a slow serial line takes a longer one. |
keep_alive | N | 60 | Seconds nothing is heard of the outstation before the device asks for the link's status, which the outstation then has timeout to answer, from 1 to 3600. 0 never asks. |
max_reconnect_interval | N | 30 | The longest wait between two tries to connect, in seconds, from 1 to 300. The wait doubles from one second while the tries fail. |
A device that fails these checks does not start, and its state is Configuration error; the message names the field. Examples are a missing or wildcard address, a link address that is missing or above 65519, the two link addresses alike, an interval or a timeout out of range, or a switch that is not true or false. A number is read whole: NaN, an infinity, or a fraction is refused, never converted. The port, the two link addresses, and a point's index are a number or its digits and nothing else. A field in seconds also takes a duration of whole seconds (30s, 5m, 1h) or ${NAME}, an environment variable of Director's that holds one. A switch is true or false in any case, or 1 or 0.
A device that polls no class on an interval and asks for no unsolicited responses says so at start: after the integrity poll it makes when it connects, it reads only what the outstation sends unasked, and what the outstation says it holds.
Session
Polls
- The integrity poll reads classes 1, 2, 3, and 0, in that order, in one request, so the outstation answers what changed before what is. It is the first request of every connection, is made again every
integrity_interval, and is made at once when the outstation says it restarted or lost events. Its answer gives every point's present value, which is what the point datasets write, and the events the outstation held. - The event polls read one class each, by a request of its own, every
classN_interval. Read alone, a class is known for every event its answer carries, and is written with it. - A class the outstation says it holds events of is read at once, whatever its interval. A class whose interval has come is read before one the outstation names, and after three reads in a row on the outstation's word alone the device rests a second, so an outstation whose input chatters cannot keep the other classes from their intervals.
Unsolicited Responses
Unsolicited responses are taken whenever they come, in the middle of an answer too, and their events are written like any other. With unsolicited: true, the device asks for them (classes 1, 2, and 3) once the first integrity poll of a connection is answered, and again after the outstation says it restarted. An outstation that refuses, or that leaves the request unanswered twice, is read by polling. Without it, the device asks for nothing, and it never asks an outstation to stop sending them.
Confirmations
A fragment that reports events asks to be confirmed, and the outstation deletes its events once it is. The device confirms a fragment once its events' records have a stream to be written to. While that stream cannot be opened, the fragment is not confirmed: the events stay with the outstation, which reports them again when asked, 30 seconds later.
Events are delivered at least once. When a confirmation does not reach the outstation, as when the connection ends between the record and the confirmation, the outstation reports the event again and it is written twice, with the same dnp3.event.time and a later event.sequence.
Connection and Link
- An outstation that does not answer is a Connection error and is asked again after a second. After three requests in a row left unanswered, the device closes the connection and opens another.
- The link. When nothing is heard of the outstation for
keep_aliveseconds, the device asks for the link's status. An outstation that does not answer withintimeouthas gone: the connection is closed and opened again. - A connection that cannot be made, or ends, is made again after one second, doubling at every failure up to
max_reconnect_interval, for as long as the device runs.
Any change to the device or to its datasets restarts it: its connection is closed and opened again, and it starts with an integrity poll. The outstation keeps the events of the meantime for Director's session, and they are read after it.
What It Sends
The device's application layer writes three requests and can build no other:
| Function code | When | What it changes at the outstation |
|---|---|---|
READ (1) of class data | The integrity poll and the event polls | Nothing |
CONFIRM (0) | For a fragment that asks for one, once its records have a stream | The outstation deletes that fragment's events from Director's own session |
ENABLE_UNSOLICITED (20) of classes 1, 2, and 3 | Only with unsolicited: true: once a connection, and again after a restart | What the outstation reports to Director's own session, and nothing else |
It never sends a control (SELECT, OPERATE, DIRECT_OPERATE), a WRITE, a freeze of counters, a cold or warm restart, time synchronization, DISABLE_UNSOLICITED, a class assignment, a file transfer, or an authentication request.
Datasets
The device's definitions are its datasets. In the console they are the datasets and profiles assigned to the device in its
| Dataset | Definition | Definition ID | Kind | One record per |
|---|---|---|---|---|
point | dnp3_point_collector | 34150 | metric | reading of the dataset's table |
event | dnp3_event_collector | 69 | log | event the outstation reports |
- Any number of point datasets. Each is a table of its own on an interval of its own: a bay or a feeder each.
- One event dataset. With several, every event is written to each.
- Events are read whether there is an event dataset or not, because they keep the tables' values current. Without one they are confirmed and written nowhere.
- Nothing assigned is allowed. The device then checks every minute that it reaches its outstation: it connects, asks for the link's status, which leaves the outstation's events where they are, and closes the connection.
- A dataset that cannot be read is left out, with a warning in the device's log that names it and says why: a table that is not a name, a point whose type or index is not valid, a point or a name listed twice, or a table with no point. The rest of the device runs. With every dataset left out, the device stops with Configuration error.
Points
A dnp3_point_collector dataset is a table of the points it lists, each with the field its value is written to. The device keeps the present value and flags of every listed point as the outstation last reported them, by an integrity poll's answer or by an event after it, and writes the table every interval seconds as one record. So the values are as fresh as the outstation's reporting: an analog input within its deadband holds the value of the last integrity poll.
| Field | Required | Default | Description |
|---|---|---|---|
table | Y | - | The name the dataset's values sit under in a record: letters, digits, and underscores, at most 64, not starting with a digit. flags, master, outstation, and table are reserved. |
interval | N | 60 | Seconds between two records, 1 or more |
labels | N | - | An object of names and values written on every record as ECS labels. At most 32; a name is letters, digits, underscores, and hyphens, and a value at most 256 characters. |
pipeline_name | N | the device's | The pre-processing pipeline the dataset's records enter |
A dataset's inputs are its points, at most 1024:
| Field | Required | Description |
|---|---|---|
name | N | The field the value is written to: letters, digits, and underscores, at most 64, not starting with a digit, once in the table. Left out, the point's type and index (analog_input_6). |
type | Y | The point's type (below) |
index | Y | The point's index at the outstation, from 0 to 65535. A type and an index are listed once in a table; several tables may list one point. |
| Type | Written as | Also read as |
|---|---|---|
binary_input | true or false | bi, g1, 1 |
double_bit_input | intermediate, determined_off, determined_on, or indeterminate | dbi, double_bit, g3, 3 |
binary_output_status | true or false | binary_output, bo, bos, g10, 10 |
counter | A whole number | ctr, binary_counter, g20, 20 |
frozen_counter | A whole number: the counter's value when it was last frozen | fctr, g21, 21 |
analog_input | A number | ai, analogue_input, g30, 30 |
analog_output_status | A number: the value the output is set to | analog_output, ao, aos, g40, 40 |
A type is read in any case, with spaces or hyphens for its underscores, so a point list is read as its tool exported it (Analog Input, AI). A counter and its frozen counter share an index: frozen counter 10 is counter 10, frozen.
A point must be in class 0, or report events, to be read. The device reads by class only, never single points. A point the outstation has in no class, and that reports no event, is never read: assign it to class 0 at the outstation.
No table is written before the outstation has answered an integrity poll over the connection, or while its last request went unanswered. A point the outstation has not reported is left out of the record, and the device's log says so once for as long as it lasts. A record with no point is not written.
Events
A dnp3_event_collector dataset writes every event the outstation reports, of every point, listed in a table or not: a record each, in the order the outstation delivered them, whichever way they came. It takes labels and pipeline_name, and nothing else: it has no interval.
Records
Every record is one ECS 8.11 document with the following fields:
| Field | Value |
|---|---|
@timestamp | When Director wrote the reading, or received the event |
ecs.version | 8.11.0 |
event.module | dnp3 |
observer.name | The device's name in the console |
observer.hostname | The Director's host name |
observer.vendor, observer.product | VirtualMetric, DNP3 |
server.address, server.port | The address the device is configured with, the outstation's or its terminal server's, and its port |
server.ip | The address, when it is an IP address |
network.protocol, network.transport | dnp3, tcp |
labels.<name> | The dataset's labels, when it has any |
Point Records
event.dataset is dnp3.point, event.kind metric, event.category host, and event.type info.
| Field | Value |
|---|---|
dnp3.point.outstation | The outstation's link address |
dnp3.point.master | Director's link address |
dnp3.point.table | The dataset's table |
dnp3.point.<table>.<name> | The present value of each point that has one, as Points writes it |
dnp3.point.flags.<name> | The point's flags: a list of the ones that are set |
{
"@timestamp": "2026-10-14T09:00:00Z",
"ecs": { "version": "8.11.0" },
"event": { "module": "dnp3", "dataset": "dnp3.point", "kind": "metric", "category": ["host"], "type": ["info"] },
"observer": { "vendor": "VirtualMetric", "product": "DNP3", "name": "substation-rtu", "hostname": "director01" },
"server": { "address": "192.0.2.70", "ip": "192.0.2.70", "port": 20000 },
"network": { "protocol": "dnp3", "transport": "tcp" },
"labels": { "substation": "elm-street" },
"dnp3": {
"point": {
"outstation": 1024,
"master": 3,
"table": "feeder",
"feeder": { "breaker_52a": true, "breaker_position": "determined_on", "bus_voltage": 13.84, "operations": 41 },
"flags": {
"breaker_52a": ["online"],
"breaker_position": ["online"],
"bus_voltage": ["online"],
"operations": ["online"]
}
}
}
}
The values sit under the table's name, so two tables can name a field alike with different types without one mapping conflicting with the other.
Event Records
event.dataset is dnp3.event, event.kind event, event.category host, event.type change, and event.action the kind of event: binary_input_event, double_bit_input_event, binary_output_event, counter_event, frozen_counter_event, analog_input_event, or analog_output_event.
| Field | Value |
|---|---|
message | A summary, such as binary_input 0 (breaker_52a): false [online] at 2026-10-14T08:59:58.123Z |
event.sequence | The event's place among the events the device has written since it started, from 1 |
dnp3.event.outstation, dnp3.event.master | The session's two link addresses |
dnp3.event.point_type | The point's type, as Points names it |
dnp3.event.index | The point's index |
dnp3.event.name | The name the device's point datasets give the point, the first that lists it; absent for a point no table lists |
dnp3.event.state | A binary input's or a binary output's state; for a double-bit input, true or false when it is determined on or off |
dnp3.event.double_bit | A double-bit input's state |
dnp3.event.count | A counter's or a frozen counter's value |
dnp3.event.value | An analog value; absent for a float that is not a number |
dnp3.event.flags | The flags that are set |
dnp3.event.time | The outstation's own time of the event, in UTC with its milliseconds, when the object carries one |
dnp3.event.time_quality | synchronized or unsynchronized; absent with the time |
dnp3.event.class | The event's class, 1 to 3, when the poll that read it read that class alone. Absent for an event an integrity poll or an unsolicited response delivered. |
dnp3.event.group, dnp3.event.variation | The object's group and variation |
dnp3.event.unsolicited | Whether the outstation sent it unasked |
{
"@timestamp": "2026-10-14T09:00:05Z",
"message": "binary_input 0 (breaker_52a): false [online] at 2026-10-14T08:59:58.123Z",
"ecs": { "version": "8.11.0" },
"event": {
"module": "dnp3", "dataset": "dnp3.event", "kind": "event", "category": ["host"], "type": ["change"],
"action": "binary_input_event", "sequence": 1
},
"observer": { "vendor": "VirtualMetric", "product": "DNP3", "name": "substation-rtu", "hostname": "director01" },
"server": { "address": "192.0.2.70", "ip": "192.0.2.70", "port": 20000 },
"network": { "protocol": "dnp3", "transport": "tcp" },
"dnp3": {
"event": {
"outstation": 1024,
"master": 3,
"point_type": "binary_input",
"index": 0,
"name": "breaker_52a",
"state": false,
"flags": ["online"],
"time": "2026-10-14T08:59:58.123Z",
"time_quality": "synchronized",
"class": 1,
"group": 2,
"variation": 2,
"unsolicited": false
}
}
}
@timestamp is Director's, not the outstation's: an outstation's clock may be unset, or hours off, and a record dated by it would be written into the past or the future. The outstation's time is kept beside it in dnp3.event.time, with what it is worth in dnp3.event.time_quality, and a pipeline that trusts the outstation's clock may date the record by it.
Flags
Flags are written by name. A point in good standing has online alone; a list without it, an empty one among them, is a point the outstation is not reading.
| Flag | Of | Meaning |
|---|---|---|
online | Every type | The point is read, and the value is its present one |
restart | Every type | The value has not been read since the outstation restarted |
comm_lost | Every type | The outstation has lost touch with what it reads the point from: the value is the last it had |
remote_forced | Every type | The value is one a master forced the point to |
local_forced | Every type | The value is one forced at the outstation |
chatter_filter | Binary and double-bit inputs | The input changes faster than the outstation reports |
rollover | Counters | The counter passed its highest value and started again |
discontinuity | Counters | The count is not continuous with the one before |
over_range | Analog values | The value is past what its object can carry |
reference_error | Analog values | The reference the value is measured against has failed |
What the Outstation Says
Every response carries the outstation's internal indications. The device acts on them, and its log says when one is set and when it is cleared:
- Events of a class held (
CLASS_1_EVENTStoCLASS_3_EVENTS): the class is read at once (see Polls). - Restart (
DEVICE_RESTART): said once, for as long as it is set. The device never clears it, which takes a write. When it is set in the middle of a connection, or the outstation sends an unsolicited response with no object, as one does when it has restarted, the device reads every point again with an integrity poll, and asks for unsolicited responses again when set to. - Events lost (
EVENT_BUFFER_OVERFLOW): said, and an integrity poll is made at once. - Time needed (
NEED_TIME): said once. The device never sets the outstation's clock; while a response asks for the time, the times of the outstation's own clock in it are writtenunsynchronized. - Trouble (
DEVICE_TROUBLE,CONFIG_CORRUPT): said, and again every 30 minutes while it lasts. - Local control (
LOCAL_CONTROL): said once, for as long as it is set. It is how the outstation stands while its outputs are worked on, not a fault.
Device State
The connection state follows the outstation:
- Connected while the outstation answers the device's requests.
- Connection error while the connection cannot be made, has ended, or stands while the outstation does not answer. An outstation that has never answered over the connection usually means the two link addresses are not the ones it knows: an outstation answers only what is sent to its own address by a master it knows.
- Authentication error while the outstation answers a read with a challenge of DNP3 Secure Authentication (see Not Supported).
- Configuration error for a configuration that cannot run (see Configuration).
Each failure is said once in the device's log with what to check, again every 30 minutes for as long as it lasts, and its end once.
The DNP3 device is not simulated. A point dataset's records are the values of your own points, and an event's the outstation's own, shapes a generated sample cannot stand in for.
Console
In the console, go to
General settings , as on every device.Connection settings :Address ,Port (20000),Master address , andOutstation address . The two link addresses are required and have no default. A note above the fields says the outstation needs a master session of its own for the director: an outstation keeps its events for each master apart and deletes them once that master confirms them, so a device that shared another master's session would take that master's events from it (see Synopsis). The note is shown in the wizard only.Polling settings :Integrity poll interval (3600),Class 1 poll interval ,Class 2 poll interval , andClass 3 poll interval (5each,0for none), andUnsolicited responses (disabled). A warning shows while all four intervals are0and unsolicited responses are disabled.Advanced settings :Timeout (5),Keepalive time (60,0for none), andMaximum reconnect interval (30).Data collection : the DNP3 datasets the device reads.Review and create .
The device's page has the same tabs,
The console refuses some values the director would take in a configuration file: a number field of the device that is blank or holds a sign, a duration, or an environment variable (the director reads a blank one as its default), and a host name in
DNP3 datasets are created in
DNP3 Points , a point dataset:Table name ,Interval (Optional) ,Points ,Labels , andPre-processing pipeline (Optional) . A point is aPoint name (Optional) , aPoint type (Binary input ,Double-bit input ,Binary output status ,Counter ,Frozen counter ,Analog input , orAnalog output status ), and aPoint index . A point with no name is named by its type and index, and the table shows that name followed by (automatic).DNP3 Events , the event dataset:Labels andPre-processing pipeline (Optional) only. A device needs one: with several, every event is written to each.
bus_voltage,analog_input,6. A name can be left empty, as ,analog_input,6. A first line that names the columns sets their order, and a type is read as point lists write it, as Analog Input, AI, or 30 (see Points). A line that is a point the table already lists, under the same name, is passed over, so a list can be imported again once its refused lines are put right. A line that clashes with the table in any other way is refused with its line number, and the other lines are imported.
Not Supported
- DNP3 Secure Authentication (SAv2, SAv5). An outstation that challenges the device's reads is not read, and the device's state is Authentication error: give Director a session the outstation does not ask to authenticate. The device answers no challenge, and asks again every five minutes, no sooner.
- DNP3 over TLS, over UDP, and a serial line of Director's own. The device reaches an outstation over TCP, or through a terminal server over TCP.
- Several outstations on one multi-drop line, behind one terminal server port, each read by a device of its own: every device has a connection and a schedule of its own, so their requests would collide on the line.
- An outstation that dials its master. The device always dials.
- Reads of single points or ranges. The device reads by class only.
- Controls, writes, freezes, restarts, and time synchronization (see What It Sends).
Compatibility
The device implements the parts of the DNP3 link layer, transport function and application layer that this page describes, as the standard defines them. If one of your outstations answers differently, contact VirtualMetric support with its make and model.
Examples
Substation RTU
Reading a substation's RTU as master 3, with a feeder's points every 60 seconds, a revenue meter's every 15 minutes, and every event the RTU reports... | |
Relay Behind a Terminal Server
Reading a protection relay on a 9600-baud serial line through its terminal server. The slow line takes a longer timeout and less frequent polls; class 3 is read only at the integrity polls and when the relay says it holds some. The point types are written as the relay's point list writes them... | |
Unsolicited Reporting
Reading a pumping station's RTU that reports its events as they happen. No class is polled on an interval; every point is read again every 15 minutes... | |