DNP3 Master Driver
The ESF DNP3 driver provides support for the DNP3 master protocol.
Features:
- Support for Serial and TCP/IP.
- Support for polled read operations, commands and unsolicited messaging.
- Support for TLS
- Support for UDP (starting from 2.1.0)
Driver global configuration
The configuration of this driver depends on the type of transport used (SERIAL or IP).
In case of IP transport, only IP address and port of the target device are required, in this case an unsecured channel will be used.
If the transport type is IP, the driver allows to use TLS to create a secure channel. TLS can be enabled by setting the ip.tls.enable parameter to true
.
If TLS is enabled, the TLS related options must be properly specified.
In case of SERIAL transport, the path of the used serial port (usually /dev/ttyxxxx) and port configuration (speed, data bits, parity, stop bits) is required.
UDP support
Knows issues/limitations
Version 2.1.0 is affected by OpenDNP3 issue https://github.com/dnp3/opendnp3/issues/405.
This can cause connection problems and log flooding if the drivers receives UDP communication related ICMP errors.
Until the issue is fixed upstream, a possible workaround is to filter ICMP error messages using firewall.
This can be done adding the following iptables rules on the gateway, for example by adding them in /etc/sysconfig/iptables
:
-A INPUT -p icmp -m icmp --icmp-type port-unreachable -j DROP
-A INPUT -p icmp -m icmp --icmp-type host-unreachable -j DROP
While this should prevent the issue, these rules will cause parsing failures in ESF 6.2.0 and earlier Firewall Service. This will cause the Firewall Web UI to stop working. This will be fixed in ESF 7.0.0.
In order to restore Firewall Web UI operation, the rules above can be removed from /etc/sysconfig/iptables
and restart the gateway, this will also remove the workaround.
If possible, similar rules should be instead added to the peer so that it does not send back ICMP errors to the gateway.
The Master Driver currently supports at most one outstation per instance over UDP.
Using UDP on Docker will not work if the UDP port is exposed using the -p
docker run
argument due to port/address translation.
Configuration parameter changes
The following global configuration parameters have been changed/added in 2.1.0:
-
The UDP option has been added to the transport.type parameter. Select this option to enable UDP communication.
-
The new local.socket.address parameter must be set to the address for the local UDP socket. Setting this parameter to 0.0.0.0 will cause the driver to listen on all interfaces. This parameter is mandatory for UDP communication, it is otherwise ignored.
-
The new local.socket.port parameter must be set to the local UDP port used by the master. This is the source port that will be used by the master and the port on which it expects to receive messages from the outstation. This parameter is mandatory for UDP communication, it is otherwise ignored.
Channel Configuration
The DNP3 channel configuration is composed by the following parameters:
- Dest. Address: The destination address of the remote outstation.
The meaning of the other parameters depends on the value of the Function parameter.
-
SCAN_ALL: Performs a read operation in all objects mode (Qualifier code 6). Mandatory configuration parameters:
- SCAN Group: The object group
- SCAN Variation: The variation, or 0 for all variations
-
SCAN_RANGE8: Performs a read operation in range-index mode using an 8-bit indexes (Qualifier code 0). Mandatory configuration parameters:
- SCAN Group: The object group
- SCAN Variation: The variation, or 0 for all variations
- SCAN Idx From: The start index, must be in the [0,255] range.
- SCAN Idx To: The end index, must be in the [0,255] range.
-
SCAN_RANGE16: Performs a read operation in range-index mode using an 16-bit indexes (Qualifier code 1). Mandatory configuration parameters:
- SCAN Group: The object group
- SCAN Variation: The variation, or 0 for all variations
- SCAN Idx From: The start index, must be in the [0,65536] range.
- SCAN Idx To: The end index, must be in the [0,65536] range.
-
SCAN_COUNT8: Performs a read operation in non ranged mode using an 8-bit indexes (Qualifier code 7). Mandatory configuration parameters:
- SCAN Group: The object group
- SCAN Variation: The variation, or 0 for all variations
- SCAN Idx From: The count parameter, must be in the [0,255] range.
-
SCAN_COUNT16: Performs a read operation in non ranged mode using an 16-bit indexes (Qualifier code 8). Mandatory configuration parameters:
- SCAN Group: The object group
- SCAN Variation: The variation, or 0 for all variations
- SCAN Idx From: The count parameter, must be in the [0,65536] range.
-
SCAN_INTEGRITY: Performs a read operation for all data in classes 0, 1, 2 and 3.
-
SCAN_EVENT_CLASSES: Performs a read operation for event data in classes 1, 2 and 3.
-
CMD_AOI32: Sends a command containing a 32 bit analog output block (Group 41 Variation 1). Mandatory configuration parameters:
- CMD Index: The index of the data point.
- CMD Selected: Determines whether to perform a select and operate request (true) or a direct operate request (false).
- value.type: must be set to
INTEGER
.
-
CMD_AOI16: Sends a command containing a 16 bit analog output block (Group 41 Variation 2). Mandatory configuration parameters:
- CMD Index: The index of the data point.
- CMD Selected: Determines whether to perform a select and operate request (true) or a direct operate request (false).
- value.type: must be set to
INTEGER
.
-
CMD_AOF32: Sends a command containing a 32 bit floating point output block (Group 41 Variation 3). Mandatory configuration parameters:
- CMD Index: The index of the data point.
- CMD Selected: Determines whether to perform a select and operate request (true) or a direct operate request (false).
- value.type: must be set to
FLOAT
.
-
CMD_AOD64: Sends a command containing a 64 bit floating point output block (Group 41 Variation 4). Mandatory configuration parameters:
- CMD Index: The index of the data point.
- CMD Selected: Determines whether to perform a select and operate request (true) or a direct operate request (false).
- value.type: must be set to
DOUBLE
.
-
CMD_CROB: Sends a command containing a control relay output block (Group 12 Variation 1). Mandatory configuration parameters:
- CMD Index: The index of the data point.
- CMD Selected: Determines whether to perform a select and operate request (true) or a direct operate request (false).
- value.type: must be set to
STRING
.
-
LISTEN: Can be used to define a channel in listening mode, this value of the function parameter must be set if the listen flag is set to true. Channels of this type can be used for receiving data contained in unsolicited messages. Relevant configuration parameters:
- SCAN Group: If specified, enables notification filtering basing on object group. In this case only the data points whose object group matches the specified group will be notified.
- SCAN Variation: If specified, enables notification filtering basing on variation. In this case only the data points whose variation group matches the specified group will be notified.
Data representation
Data point value representation
The driver uses a JSON representation for returning the value of the data points received during requests or unsolicited messages. A data point object is represented as a JSON object with the following properties:
- Index (
number
): reports the data point index. - Quality (
number
): reports the received quality value as an unsigned integer. - Timestamp (
number
): reports the timestamp in milliseconds since Unix Epoch. - Value (
boolean
,number
orstring
): reports the received value. The value type depends on the DNP3 data point typesboolean
: in case of a single binary valuestring
: in case of a double bit binary value, in this case the reported string will be one of the following:INTERMEDIATE
DETERMINED_OFF
DETERMINED_ON
INDETERMINATE
number
: for the other cases, if the value object represents a date, the value of this field will report the value in terms of milliseconds since Unix Epoch.
Example:
{
"Index": 0,
"Quality": 2,
"Timestamp": 0,
"Value": false
}
Sequence of data points representation
The contents of a sequence of data points relative to the same object header represented using a JSON object containing the following fields:
-
Type (
string
): A sting representing the data point type, possible values are: -
AnalogInput
-
AnalogOutputStatus
-
BinaryInput
-
BinaryOutputStatus
-
Counter
-
DoubleBitBinaryInput
-
FrozenCounter
-
DNPTime
-
Group (
integer
): The object group of the received data points. -
Variation (
integer
): The variation of the received data points. -
Event (
boolean
): Reports if frame contains event data. -
TimestampQuality (
string
): Reports the timestamp validity and synchronization status. Possible values are: -
INVALID
-
SYNCHRONIZED
-
UNSYNCHRONIZED
-
Values (
array
): A JSON array reporting the data points.
Example:
{
"Type":"BinaryInput",
"Group":1,
"Variation":2,
"Event":false,
"TimestampQuality":"INVALID",
"Values":
[
{"Index":0,"Quality":2,"Timestamp":0,"Value":false},
{"Index":1,"Quality":2,"Timestamp":0,"Value":false},
{"Index":2,"Quality":2,"Timestamp":0,"Value":false},
{"Index":3,"Quality":2,"Timestamp":0,"Value":false},
{"Index":4,"Quality":2,"Timestamp":0,"Value":false},
{"Index":5,"Quality":2,"Timestamp":0,"Value":false},
{"Index":6,"Quality":2,"Timestamp":0,"Value":false},
{"Index":7,"Quality":2,"Timestamp":0,"Value":false},
{"Index":8,"Quality":2,"Timestamp":0,"Value":false},
{"Index":9,"Quality":2,"Timestamp":0,"Value":false}
],
}
Response representation
A response to a driver read request will be represented as a JSON array containing a JSON object per object header:
Example:
[
{
"Type":"BinaryInput",
"Group":1,
"Variation":2,
"Event":false,
"TimestampQuality":"INVALID",
"Values":
[
{"Index":0,"Quality":2,"Timestamp":0,"Value":false},
{"Index":1,"Quality":2,"Timestamp":0,"Value":false},
{"Index":2,"Quality":2,"Timestamp":0,"Value":false},
{"Index":3,"Quality":2,"Timestamp":0,"Value":false},
{"Index":4,"Quality":2,"Timestamp":0,"Value":false},
{"Index":5,"Quality":2,"Timestamp":0,"Value":false},
{"Index":6,"Quality":2,"Timestamp":0,"Value":false},
{"Index":7,"Quality":2,"Timestamp":0,"Value":false},
{"Index":8,"Quality":2,"Timestamp":0,"Value":false},
{"Index":9,"Quality":2,"Timestamp":0,"Value":false}
],
},
{
"Type":"DoubleBitBinaryInput",
"Group":3,
"Variation":2,
"Event":false,
"TimestampQuality":"INVALID",
"Values":
[
{"Index":0,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"},
{"Index":1,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"},
{"Index":2,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"},
{"Index":3,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"},
{"Index":4,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"},
{"Index":5,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"},
{"Index":6,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"},
{"Index":7,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"},
{"Index":8,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"},
{"Index":9,"Quality":2,"Timestamp":0,"Value":"INTERMEDIATE"}
],
}
]
Unsolicited responses representation
Unsolicited response can be retrieved using channels in listen mode.
The received data is provided as a JSON object having the same structure described in the Sequence of data points representation section.
Note that unlike the response representation presented above, the toplevel JSON element is an object instead of an array.
Example:
{
"TimestampQuality": "INVALID",
"Group": 2,
"Variation": 1,
"Event": true,
"Values": [
{ "Index": 2, "Quality": 129, "Timestamp": 0, "Value": true}
]
}
Using the received values in Kura Wires
The Driver and Wires model support a limited set of data types, most of them are of numeric type and unstructured (arrays and complex data structures are not supported out of the box).
The DNP3 master driver uses the STRING
type and the JSON format to overcome this limitation and represent the metadata associated with every data point value (index, quality, timestamp, type etc.).
This has the side effect of making the received data not directly consumable by wire components that expect simpler data types.
If the metadata is not required, it is possible to create a Javascript Filter component that can be attached to a DNP3 asset that converts the received data into simpler Wires values, reporting only the DNP3 data point value.
An example code for such script filter can be the following:
var processMessage = function (obj, record) {
var type = obj.Type
var dataPoints = obj.Values
for (var i = 0; i<dataPoints.length; i++) {
var dataPoint = dataPoints[i]
var key = (type + '_' + dataPoint.Index).toString()
var value = dataPoint.Value
if (typeof value == 'number') {
record[key] = newDoubleValue(value)
} else if (typeof value == 'string') {
record[key] = newStringValue(value)
} else if (typeof value == 'boolean') {
record[key] = newBooleanValue(value)
}
}
}
var processMessages = function (array, record) {
for (var i = 0; i<array.length; i++) {
var message = array[i]
if (message instanceof Object) {
processMessage(message, record)
}
}
}
var outputRecord = newWireRecord()
var inputRecords = input.records
for (var i = 0; i < inputRecords.length; i++) {
var inputRecord = inputRecords[i]
for (var key in inputRecord) {
if (key == 'assetName') {
continue
}
var value = inputRecord[key]
if (value.getType() == STRING) {
try {
var obj = JSON.parse(value.getValue())
if (Array.isArray(obj)) {
processMessages(obj, outputRecord)
} else {
processMessage(obj, outputRecord)
}
} catch (err) {
logger.warn('failed to process channel {}', key)
}
}
}
}
output.add(outputRecord)
The attached script produces a single output wire record containing a property for each data point, the property key is the point Type
from the Json representation concatenated with the point index by an underscore, the value will be the data point value as a Wires DOUBLE
, BOOLEAN
or STRING
.
The script supports both channels in listen and read mode.
Creating a wire graph composed by the following components:
Timer -> DNP3Asset -> Script -> Logger
where
Timer
is a Wires timerDNP3Asset
is an Asset attached to the DNP3Driver with a single channel that performs an integrity scanScript
is a ScriptFilter with the code aboveLogger
is a Logger inVERBOSE
mode
produces an output similar the following one on the log (/var/log/kura.log
):
INFO o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.ScriptFilter-1579167946653-27
INFO o.e.k.i.w.l.Logger - Record List content:
INFO o.e.k.i.w.l.Logger - Record content:
INFO o.e.k.i.w.l.Logger - Counter_2 : 0.0
INFO o.e.k.i.w.l.Logger - Counter_1 : 0.0
INFO o.e.k.i.w.l.Logger - Counter_0 : 0.0
INFO o.e.k.i.w.l.Logger - AnalogOutputStatus_0 : 0.0
INFO o.e.k.i.w.l.Logger - AnalogOutputStatus_2 : 0.0
INFO o.e.k.i.w.l.Logger - AnalogOutputStatus_1 : 0.0
INFO o.e.k.i.w.l.Logger - DoubleBitBinaryInput_2 : INTERMEDIATE
INFO o.e.k.i.w.l.Logger - DoubleBitBinaryInput_1 : INTERMEDIATE
INFO o.e.k.i.w.l.Logger - DoubleBitBinaryInput_0 : INTERMEDIATE
INFO o.e.k.i.w.l.Logger - FrozenCounter_0 : 0.0
INFO o.e.k.i.w.l.Logger - FrozenCounter_1 : 0.0
INFO o.e.k.i.w.l.Logger - FrozenCounter_2 : 0.0
INFO o.e.k.i.w.l.Logger - BinaryInput_0 : false
INFO o.e.k.i.w.l.Logger - BinaryInput_1 : false
INFO o.e.k.i.w.l.Logger - BinaryInput_2 : false
INFO o.e.k.i.w.l.Logger - BinaryOutputStatus_2 : false
INFO o.e.k.i.w.l.Logger - BinaryOutputStatus_1 : false
INFO o.e.k.i.w.l.Logger - BinaryOutputStatus_0 : false
INFO o.e.k.i.w.l.Logger - AnalogInput_0 : 0.0
INFO o.e.k.i.w.l.Logger - AnalogInput_1 : 0.0
INFO o.e.k.i.w.l.Logger - AnalogInput_2 : 0.0
In the example above the server contains 3 data points per mentioned type.
Command data representation
The values supplied for sending commands must be encoded as follows, depending on the value of the Function configuration parameter:
-
CMD_AOI32: an
INTEGER
value. -
CMD_AOI16: an
INTEGER
value in the [-32768, 32767] range. -
CMD_AOF32: a
FLOAT
value. -
CMD_AOD64: a
DOUBLE
value. -
CMD_CROB: a
STRING
value containing a JSON object representation of the CROB field, the JSON object must contain the following fields:-
Count (
number
): the value of the count field. -
OnTime (
number
): the value of the on time field. -
OffTime (
number
): the value of the off time field. -
ControlCode (
string
): the value of the control code field. The allowed values are the following:NUL
NUL_CANCEL
PULSE_ON
PULSE_ON_CANCEL
PULSE_OFF
PULSE_OFF_CANCEL
LATCH_ON
LATCH_ON_CANCEL
LATCH_OFF
LATCH_OFF_CANCEL
CLOSE_PULSE_ON
CLOSE_PULSE_ON_CANCEL
TRIP_PULSE_ON
TRIP_PULSE_ON_CANCEL
Example:
{ "Count": 1, "OnTime": 100, "OffTime": 100, "ControlCode":"CLOSE_PULSE_ON" }
-
Note about floating point values
The driver currently does not support denormal floating point values, it is recommended to avoid using such values if possible. Denormal floating point values received from the framework will be clamped to 0 by the driver.
Updated almost 4 years ago