Blackstart Labs
blackstart_web
← Blog

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

GrpObjectV0V1V2V3
1Binary Input (static)All vars (read req.)Packed single-bitPer-point + status flags
2Binary Input Change (event)All vars (read req.)Change, no timestampChange + absolute timeChange + relative time
3Double-bit Binary Input (static)All vars (read req.)Packed two-bit statesTwo-bit + status flags
4Double-bit Binary Input Change (event)All vars (read req.)Change, no timestampChange + absolute timeChange + relative time
10Binary Output (static status)All vars (read req.)Packed ON/OFFOutput + status flags
11Binary Output Command EventEvent, no timestampEvent + time
12Binary output controlCROB (select/operate)Pattern Control BlockPattern Mask

Counters and counter events

GrpObjectV0V1V2V3V4V5V6V7V8V9V10V11V12
20Binary Counter (running)All vars (read req.)32-bit + flag16-bit + flag32-bit delta + flag16-bit delta + flag32-bit, no flag16-bit, no flag32-bit delta, no flag16-bit delta, no flag
21Frozen CounterAll vars (read req.)32-bit frozen + flag16-bit frozen + flag32-bit frozen delta + flag16-bit frozen delta + flag32-bit frozen + time-of-freeze16-bit frozen + time-of-freeze32-bit frz delta + time-of-freeze16-bit frz delta + time-of-freeze32-bit frozen, no flag16-bit frozen, no flag32-bit frz delta, no flag16-bit frz delta, no flag
22Counter Change EventAll vars (read req.)32-bit chg, no time16-bit chg, no time32-bit delta chg, no time16-bit delta chg, no time32-bit chg + time16-bit chg + time32-bit delta chg + time16-bit delta chg + time
23Frozen Counter EventAll vars (read req.)32-bit frz event, no time16-bit frz event, no time32-bit frz delta ev, no time16-bit frz delta ev, no time32-bit frz event + time16-bit frz event + time32-bit frz delta ev + time16-bit frz delta ev + time

Analog inputs, frozen values, and change events

GrpObjectV0V1V2V3V4V5V6V7V8
30Analog Input (static)All vars (read req.)32-bit int + flag16-bit int + flag32-bit int, no flag16-bit int, no flag32-bit float + flag64-bit float + flag
31Frozen Analog InputAll vars (read req.)32-bit frozen + flag16-bit frozen + flag32-bit + time of freeze16-bit + time of freeze32-bit frozen, no flag16-bit frozen, no flag32-bit float frozen64-bit float frozen
32Analog Input Change EventAll vars (read req.)32-bit chg, no time16-bit chg, no time32-bit chg + time16-bit chg + time32-bit float chg, no time64-bit float chg, no time32-bit float chg + time64-bit float chg + time
33Frozen Analog EventAll vars (read req.)32-bit frz ev, no time16-bit frz ev, no time32-bit frz ev + time16-bit frz ev + time32-bit float frz ev, no time64-bit float frz ev, no time32-bit float frz ev + time64-bit float frz ev + time

Analog outputs and output events

GrpObjectV0V1V2V3V4V5V6V7V8
40Analog Output StatusAll vars (read req.)32-bit status + flags16-bit status + flags32-bit float status64-bit float status
41Analog Output Command32-bit command block16-bit command block32-bit float cmd64-bit float cmd
42Analog Output Event32-bit + time16-bit + time32-bit float + time64-bit float + time

Time, delay, and class reads

GrpObjectV1V2V3V4
50Time and DateAbsolute timeTime + intervalLast recorded time
51Common Time-of-Occurrence (CTO)Synchronized CTOUnsynchronized CTO
52Time DelayCoarse delayFine delay
60Class data (read umbrella)Class 0 (static)Class 1 (events)Class 2 (events)Class 3 (events)

File transfer, security fragments, device support

GrpObjectV1V2V3V4V5V6V7V8
70File / auth suiteFile ID (profile-specific)Authentication fragmentFile control — commandFile control — statusFile transport — dataFile transport — statusFile descriptorFile specification string
80Internal Indications (IIN)IIN bit field
81Storage objectStorage descriptor
91Activate ConfigurationStatus

Packed BCD and special-purpose integers

GrpObjectV1V2V3
101Packed Binary-Coded DecimalSmall packed BCDMedium packed BCDLarge packed BCD
1028-bit unsigned integerOne 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.

GroupObjectVariation handling
110Octet String (static)Variation number equals string length in octets.
111Octet String EventVariation number equals string length in octets.
112Virtual Terminal Output BlockVariation sets block size; see device profile.
113Virtual Terminal Event DataVariation 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.