Field notes · May 2026
DNP3 field guide: classes, objects, and variations
This note is written from the perspective of someone who spends time commissioning masters, tuning outstation profiles, and proving behaviour under marginal links—not from the perspective of the standard document itself. DNP3 (IEEE Std 1815) is often described in abstract terms; in the field it shows up as scan lists, event buffers, variation choices on IEDs, and the gap between what a protocol stack allows and what a particular RTU or relay actually implements. Use it alongside your utility practice, vendor manuals, and lab checkout procedures. For protocol selection (when DNP3 wins versus Modbus or OPC UA), see the separate comparison article.
What DNP3 is optimising for
At its core, DNP3 is a master–outstation application protocol with a defined data link layer, transport rules, and a rich set of application objects. The outstation is the source of truth for process data, quality flags, and (where configured) time-tagged change events. The master issues reads, writes, controls, time synchronisation, file operations, and class-based polls; the outstation responds with one or more application fragments that may span multiple link-layer frames when payloads are large or the link enforces modest MTUs.
That framing matters on serial or constrained IP paths: you are not simply “reading a register map” at a fixed cadence. You are negotiating buffer state, confirming events, and (when unsolicited reporting is in play) accepting data that arrives outside the nominal scan cycle. Integrations go wrong when teams treat DNP3 like Modbus with bigger headers—same poll mindset, same expectations of instantaneous consistency—rather than as a state machine shared between master and outstation.
Logical data is addressed with an object group, a variation that selects encoding and optional metadata (flags, timestamps, float versus integer), and an index within that object space. The same physical point frequently exists in more than one object family: a breaker position might be represented as a static double-bit input (Group 3) for the HMI and as a change event (Group 4) for sequence-of-events reporting. The integrator’s job is to align those representations with the device profile, the SCADA point mapping, and the historian’s expectations for time and quality.
Performance, headroom, and where implementations disagree
DNP3 tends to perform well on lossy or high-latency paths when you size scans to the link, enable sensible confirmation behaviour, and avoid saturating the outstation with class reads faster than it can format responses or drain buffers. Throughput is bounded by the usual suspects—baud rate or IP bandwidth, maximum frame size, fragment count, and whether each fragment must be confirmed at the link layer—but also by firmware limits on how many events can be packed per response and how aggressively the device coalesces changes.
The standard defines interoperable encodings; it does not guarantee that every vendor supports every variation, nor that event buffers are deep enough for your storm scenario. Before you rely on a particular frozen-counter pattern, analog deadband behaviour, or unsolicited channel, prove it against the firmware revision on the bench: overflow handling (drop versus wrap), duplicate detection, and time quality on events are all legitimate review items in FAT/SAT checklists.
From a maintenance standpoint, DNP3 is more expensive to troubleshoot than a flat register protocol. A protocol analyser that decodes objects is nearly mandatory for non-trivial issues. The upside is that once the profile is stable, class-based reporting and explicit quality often reduce operator surprise compared with inferring health from raw bits alone.
Data classes (Class 0 through Class 3)
Data classes are a reporting contract configured on the outstation. Points and events are assigned to Class 0 for static reporting and to Class 1, Class 2, or Class 3 for buffered change data according to priority rules you define with the device configuration tool (and sometimes with Assign Class interactions from the master). The protocol does not dictate how often each class must be read. Poll intervals, scan phases, and backoff behaviour are entirely defined by the user—that is, by the SCADA vendor’s master configuration, the integrator’s scan list design, and the utility’s operating standards—not by IEEE Std 1815. The standard tells you what “Class 1 data” means when you ask for it; your project decides whether that poll is every 250 ms, every 2 s, or only after a prior fragment has been confirmed.
In practice, masters collect class data using Object 60: variation 1 returns Class 0 payloads, variation 2 returns Class 1 payloads, variation 3 returns Class 2, and variation 4 returns Class 3. That pattern is stable enough to be worth memorising. Separately, many installations enable unsolicited reporting for selected classes on IP paths where firewall policy and outstation configuration allow the outstation to initiate application-layer traffic; even then, unsolicited complements rather than replaces the master’s responsibility to confirm events and to fall back to polled class reads when the channel is asymmetric or disabled.
Example master scan pattern (intervals are illustrative only)
Consider a distribution recloser controller on a marginal radio path. A reasonable starting point—subject to link capacity, CPU load, and utility policy—might look like this in the master:
- Class 1 read every 500 ms to pick up protection-adjacent and trip-class events quickly. This number is not special to DNP3; it is an engineering choice. Some teams run faster on fiber, slower on serial; some utilities standardise on fixed scan templates per device class.
- Class 2 read every 2 s for operational changes that must not be starved but can wait briefly behind Class 1 backlog.
- Class 3 read every 5 s if the device uses Class 3 at all for lower-priority or high-volume events—confirm support first.
- Class 0 static snapshot every 4 s (or slower) for analogs and status that do not need sub-second refresh on the operator display.
The numeric cadences above are placeholders. Your acceptable latencies, jitter, and bandwidth budget determine the real values. Document them in the integration record: changing poll times without revisiting buffer depths and confirmation rules is a common source of “it worked in the lab” surprises in production.
Class 0 carries the current value of points assigned to static reporting—binary and double-bit inputs, analog inputs, running counters, output status, and similar—rather than the queued change stream. Class 1 is typically reserved for the highest-priority events the operator or protection-adjacent logic must see first: trips, critical alarms, and other changes where delayed delivery is unacceptable. Class 2 holds medium-priority events that still require reliable delivery but can be ordered after Class 1 in a multi-phase scan. Class 3, when implemented, is often used for lower-priority or higher-volume changes; treat vendor semantics here as mandatory reading, because profiles diverge more on Class 3 than on Class 0–2.
A well-run project makes the mapping from object events to classes explicit in the point book: which Group 2 indices land in Class 1 versus Class 2, whether analog change events (Group 32) are split by deadband tiers, and what happens when a class poll is skipped because the link was down. Operators care about the outcome—timely annunciation and clean SOE—not about the object numbers, but integrators live in both worlds.
Point types, quality, and timestamps
Static objects answer the question “what is the value now?” Event objects answer “what changed, and when?” with optional absolute or relative time, sequence context for acknowledgement, and flags that describe whether the sample is valid, substituted, from restart, or subject to communications loss. Those flags are not decorative: they drive alarm suppression logic, historian quality codes, and post-disturbance forensics.
Controls use dedicated command objects—binary patterns such as the Control Relay Output Block (Group 12) and analog output command blocks (Group 41)—rather than “writing” static inputs. Select-before-operate versus direct operate is a policy and device-capability discussion; DNP3 provides function codes and object layouts, while the project defines interlocking, supervision, and testing evidence.
Time synchronisation (Groups 50–52) deserves explicit commissioning. Drift between outstation time and master time shows up first as SOE arguments in meetings, not as link errors. If your architecture uses a GPS-disciplined clock in the substation, say so in the integration binder and verify that event timestamps and Class 0 snapshots remain coherent after reboot sequences.
Object groups and variations (column layout)
Each table below uses one row per object group and one column per variation index. A cell describes what that variation means for that group only; the same column number can mean something different on another row, which is why the group number always travels with the row. Variation 0, where listed, denotes an aggregate read qualifier (“all variations”) in request parsing—not a standalone payload you should expect on the wire as a permanent storage format.
Shorthand in cells is for orientation. Frozen-counter layouts, float widths, and flag octets must be verified against IEEE Std 1815 and the manufacturer’s device profile or DNP3 device description document for the firmware you ship.
Binary and double-bit inputs and outputs
| Grp | Object | V0 | V1 | V2 | V3 |
|---|---|---|---|---|---|
| 1 | Binary Input (static) | All vars (read req.) | Packed single-bit | Per-point + status flags | — |
| 2 | Binary Input Change (event) | All vars (read req.) | Change, no timestamp | Change + absolute time | Change + relative time |
| 3 | Double-bit Binary Input (static) | All vars (read req.) | Packed two-bit states | Two-bit + status flags | — |
| 4 | Double-bit Binary Input Change (event) | All vars (read req.) | Change, no timestamp | Change + absolute time | Change + relative time |
| 10 | Binary Output (static status) | All vars (read req.) | Packed ON/OFF | Output + status flags | — |
| 11 | Binary Output Command Event | — | Event, no timestamp | Event + time | — |
| 12 | Binary output control | — | CROB (select/operate) | Pattern Control Block | Pattern Mask |
Counters and counter events
| Grp | Object | V0 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 | V12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 20 | Binary Counter (running) | All vars (read req.) | 32-bit + flag | 16-bit + flag | 32-bit delta + flag | 16-bit delta + flag | 32-bit, no flag | 16-bit, no flag | 32-bit delta, no flag | 16-bit delta, no flag | — | — | — | — |
| 21 | Frozen Counter | All vars (read req.) | 32-bit frozen + flag | 16-bit frozen + flag | 32-bit frozen delta + flag | 16-bit frozen delta + flag | 32-bit frozen + time-of-freeze | 16-bit frozen + time-of-freeze | 32-bit frz delta + time-of-freeze | 16-bit frz delta + time-of-freeze | 32-bit frozen, no flag | 16-bit frozen, no flag | 32-bit frz delta, no flag | 16-bit frz delta, no flag |
| 22 | Counter Change Event | All vars (read req.) | 32-bit chg, no time | 16-bit chg, no time | 32-bit delta chg, no time | 16-bit delta chg, no time | 32-bit chg + time | 16-bit chg + time | 32-bit delta chg + time | 16-bit delta chg + time | — | — | — | — |
| 23 | Frozen Counter Event | All vars (read req.) | 32-bit frz event, no time | 16-bit frz event, no time | 32-bit frz delta ev, no time | 16-bit frz delta ev, no time | 32-bit frz event + time | 16-bit frz event + time | 32-bit frz delta ev + time | 16-bit frz delta ev + time | — | — | — | — |
Analog inputs, frozen values, and change events
| Grp | Object | V0 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 |
|---|---|---|---|---|---|---|---|---|---|---|
| 30 | Analog Input (static) | All vars (read req.) | 32-bit int + flag | 16-bit int + flag | 32-bit int, no flag | 16-bit int, no flag | 32-bit float + flag | 64-bit float + flag | — | — |
| 31 | Frozen Analog Input | All vars (read req.) | 32-bit frozen + flag | 16-bit frozen + flag | 32-bit + time of freeze | 16-bit + time of freeze | 32-bit frozen, no flag | 16-bit frozen, no flag | 32-bit float frozen | 64-bit float frozen |
| 32 | Analog Input Change Event | All vars (read req.) | 32-bit chg, no time | 16-bit chg, no time | 32-bit chg + time | 16-bit chg + time | 32-bit float chg, no time | 64-bit float chg, no time | 32-bit float chg + time | 64-bit float chg + time |
| 33 | Frozen Analog Event | All vars (read req.) | 32-bit frz ev, no time | 16-bit frz ev, no time | 32-bit frz ev + time | 16-bit frz ev + time | 32-bit float frz ev, no time | 64-bit float frz ev, no time | 32-bit float frz ev + time | 64-bit float frz ev + time |
Analog outputs and output events
| Grp | Object | V0 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 |
|---|---|---|---|---|---|---|---|---|---|---|
| 40 | Analog Output Status | All vars (read req.) | 32-bit status + flags | 16-bit status + flags | 32-bit float status | 64-bit float status | — | — | — | — |
| 41 | Analog Output Command | — | 32-bit command block | 16-bit command block | 32-bit float cmd | 64-bit float cmd | — | — | — | — |
| 42 | Analog Output Event | — | — | — | 32-bit + time | 16-bit + time | — | — | 32-bit float + time | 64-bit float + time |
Time, delay, and class reads
| Grp | Object | V1 | V2 | V3 | V4 |
|---|---|---|---|---|---|
| 50 | Time and Date | Absolute time | Time + interval | Last recorded time | — |
| 51 | Common Time-of-Occurrence (CTO) | Synchronized CTO | Unsynchronized CTO | — | — |
| 52 | Time Delay | Coarse delay | Fine delay | — | — |
| 60 | Class data (read umbrella) | Class 0 (static) | Class 1 (events) | Class 2 (events) | Class 3 (events) |
File transfer, security fragments, device support
| Grp | Object | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 |
|---|---|---|---|---|---|---|---|---|---|
| 70 | File / auth suite | File ID (profile-specific) | Authentication fragment | File control — command | File control — status | File transport — data | File transport — status | File descriptor | File specification string |
| 80 | Internal Indications (IIN) | IIN bit field | — | — | — | — | — | — | — |
| 81 | Storage object | Storage descriptor | — | — | — | — | — | — | — |
| 91 | Activate Configuration | Status | — | — | — | — | — | — | — |
Packed BCD and special-purpose integers
| Grp | Object | V1 | V2 | V3 |
|---|---|---|---|---|
| 101 | Packed Binary-Coded Decimal | Small packed BCD | Medium packed BCD | Large packed BCD |
| 102 | 8-bit unsigned integer | One octet value | — | — |
Variable-length octet and virtual-terminal objects
Groups 110–113 do not share the same “fixed variation set” mental model as binary or analog tables. Here, the variation number typically selects the payload length in octets (or a device-specific block size). Treat the profile as authoritative; generic tables cannot express every permitted length your relay may expose.
| Group | Object | Variation handling |
|---|---|---|
| 110 | Octet String (static) | Variation number equals string length in octets. |
| 111 | Octet String Event | Variation number equals string length in octets. |
| 112 | Virtual Terminal Output Block | Variation sets block size; see device profile. |
| 113 | Virtual Terminal Event Data | Variation sets event payload size; see device profile. |
Mapping to Modbus, IEC 61850, and OPC UA at the gateway
Gateways are where semantics are won or lost. Modbus coils and discrete inputs map cleanly to binary outputs and binary inputs only at the level of raw state; they do not, by themselves, carry DNP3-style per-point flags or classed event queues. If the SCADA still expects trip-class reporting with timestamps, either the gateway must synthesise events (rare, and brittle) or the architecture must retain native DNP3 to the edge device for those points. Document engineering units, endianness, scaling, and who owns the clock on each hop.
IEC 61850 and OPC UA integrations usually succeed when you map logical nodes or typed nodes to DNP3 indices once, then regression-test controls and frozen snapshots under load. Pay attention to duplicate suppression: if both a polled static path and an event path can represent the same physical change, the historian must not double-count it.
End-to-end testing should include Class 0 versus Class 1/2 ordering after link resets, unsolicited enable/disable cycles, buffer-full behaviour, and select-before-operate timing through the translator. The failure mode is almost never “the standard was ambiguous”; it is that two stacks interpreted optional features differently.
This article is an integrator-oriented summary. It is not a substitute for IEEE Std 1815, your utility’s DNP3 profile, cyber requirements, or the device manual for the firmware installed on site. When documentation conflicts, follow the signed-off project basis—and keep the protocol capture that proved it.
