Skip to content

Normalized DNP3 Control Event

Raw protocol detail is essential for engineering and forensics, but many operational consumers need device, asset, action, severity, and a readable description. The DNP3 normalizer enriches parsed events with an optional customer asset map.

Example normalized DNP3 event flow through the evidence pipeline

Figure — Normalized DNP3 events preserve operation meaning, context, and decision outcome.

Representative decoded input

DNP3
  source: 3
  destination: 100
  function: Direct Operate
  object: Group 12 Variation 1
  point: CROB 0
  opcode: Latch On

Customer asset map

devices:
  - dnp3_address: 3
    name: SCADA Master
    role: master

  - dnp3_address: 100
    name: North RTU
    role: outstation
    location: North Substation

point_maps:
  outstation_100:
    - point_key: "100:CROB:0"
      asset_id: F12_BREAKER
      asset_type: breaker
      intent: F12 breaker control
      critical: true

Representative normalized output

{
  "timestamp": "2026-07-15T21:00:00Z",
  "protocol": "dnp3",
  "event_type": "control_command",
  "category": "control",
  "severity": "high",
  "frame_number": 1842,
  "network": {
    "src_ip": "10.20.1.10",
    "dst_ip": "10.20.2.100",
    "src_port": 49152,
    "dst_port": 20000
  },
  "industrial": {
    "src_device_addr": 3,
    "dst_device_addr": 100,
    "src_device_name": "SCADA Master",
    "dst_device_name": "North RTU",
    "src_device_role": "master",
    "dst_device_role": "outstation",
    "dst_device_location": "North Substation",
    "operation": "direct_operate",
    "function_code": 5,
    "function_name": "Direct Operate",
    "asset_type": "breaker",
    "asset_id": "F12_BREAKER",
    "point_key": "100:CROB:0",
    "point_type": "CROB",
    "point_index": 0,
    "intent": "F12 breaker control",
    "action": "Close",
    "critical": true
  },
  "description": "SCADA Master sent DIRECT OPERATE: Close command to F12_BREAKER (breaker) on North RTU — F12 breaker control",
  "process_assets": [
    {
      "raw": {
        "group": 12,
        "variation": 1,
        "index": 0,
        "point_key": "100:CROB:0",
        "point_type": "CROB",
        "crob": {
          "tcc": "Close",
          "opcode": "Latch On",
          "count": 1,
          "on_time_ms": 0,
          "off_time_ms": 0
        }
      },
      "mapped": {
        "found": true,
        "asset_id": "F12_BREAKER",
        "asset_type": "breaker",
        "intent": "F12 breaker control",
        "critical": true,
        "label": "F12_BREAKER Close"
      }
    }
  ]
}

The schema also exposes structured process_assets containing raw group, variation, index, value/state/CROB fields, quality, event time, and mapped asset data. Unknown devices or points can be marked in discovery for asset-mapping review.

Operational uses

  • SOC and control-room event review;
  • asset-specific audit and reporting;
  • investigation of critical commands;
  • discovery of unmapped DNP3 devices and points;
  • correlation with Rule Engine decisions in a downstream data platform.

See Industrial Event Normalization for the classification catalog, discovery workflow, operating modes, and reporting views.

Normalization does not replace the raw evidence. Retain the parsed protocol event and the normalized representation when forensic traceability is required.

These representative DNP3 outputs show how L2Proxy turns parser-level detail into two complementary products:

  • operational text that a control-room or OT-security user can understand quickly;
  • filterable classification and asset fields for dashboards, alerts, reports, and SIEM rules.

The examples use customer-friendly asset names. Actual names, locations, criticality, units, and intent come from the deployed YAML asset map.

Compact views

Tag blocks below are concise projections of the event for readability. In the full JSON event, asset fields are grouped under industrial, discovery flags under discovery, and network fields under network; PostgreSQL stores the principal fields as indexed columns for direct filtering.

1. Critical breaker Direct Operate

Technical observation

Function 5 · Group 12 Variation 1 · point 0 · TCC Close · opcode Latch On

Operational text

SCADA Master sent DIRECT OPERATE: Close command to CB-101 (breaker) on RTU-South — F12 circuit breaker control

Filterable tags and context

{
  "protocol": "dnp3",
  "event_type": "control_command",
  "category": "control",
  "severity": "high",
  "operation": "direct_operate",
  "asset_id": "CB-101",
  "asset_type": "breaker",
  "point_type": "CROB",
  "action": "Close",
  "critical": true,
  "alarm": false
}

Useful for: switching audit, critical-control notification, operator activity review, and correlation with an approved work order.

2. Select-Before-Operate sequence

The same asset can produce two separately classified records:

[SELECT]
SCADA Master sent SELECT: Close command to CB-101 (breaker) on RTU-South — F12 circuit breaker control

event_type=control_sequence  category=control  severity=medium
operation=select             sequence=select_operate
[OPERATE]
SCADA Master sent OPERATE: Close command to CB-101 (breaker) on RTU-South — F12 circuit breaker control

event_type=control_command   category=control  severity=high
operation=operate            sequence=select_operate

Useful for: reconstructing switching steps and placing the Rule Engine's stateful SBO accept/drop decision beside the operator-readable event history.

Classification is not correlation proof

sequence=select_operate identifies the DNP3 control workflow. Proof that a specific Operate consumed a matching Select comes from the stateful Rule Engine policy.

3. Routine monitoring request

Operational text

SCADA Master requested BI/AI objects from RTU-South

Filterable tags

{
  "event_type": "monitoring_read",
  "category": "monitoring",
  "severity": "low",
  "operation": "read",
  "point_type": "BI/AI"
}

Useful for: retaining an operational baseline while excluding routine polling from high-priority control and security views.

4. Unsolicited multi-asset status update

Operational text

RTU-South → SCADA Master: CB-101 [F12 breaker status] = CLOSED; REC-217 [Feeder 7 recloser status] = OPEN; BUS-VOLTAGE [11 kV bus] = 10.84 kV; (+1 unidentified points)

Filterable tags and context

{
  "event_type": "unsolicited_event",
  "category": "event",
  "severity": "high",
  "operation": "unsolicited_response",
  "critical": true,
  "unknown_point": true,
  "process_assets": [
    {"mapped": {"asset_id": "CB-101", "critical": true}},
    {"mapped": {"asset_id": "REC-217"}},
    {"mapped": {"asset_id": "BUS-VOLTAGE"}},
    {"mapped": {"found": false}}
  ]
}

Useful for: situational awareness, spontaneous-change review, multi-point event display, and identifying incomplete point maps without hiding known asset updates.

5. RTU restart and time synchronization need

Operational text

RTU-East → SCADA Master: IIN: DEVICE_RESTART, NEED_TIME

Filterable tags

{
  "event_type": "response",
  "category": "response",
  "severity": "critical",
  "operation": "response",
  "alarm": true,
  "raw_details": {
    "iin_flags": ["DEVICE_RESTART", "NEED_TIME"]
  }
}

Useful for: unexpected RTU restart detection, post-restart health checks, time-sync operations, and correlation with maintenance or power events.

6. Lost communication quality on a process point

Operational text

RTU-North → SCADA Master: PUMP-2-RUN [Raw-water pump running feedback] = OPEN [COMM_LOST]

Structured point evidence

{
  "event_type": "response",
  "category": "response",
  "industrial": {
    "asset_id": "PUMP-2-RUN",
    "asset_type": "pump_status"
  },
  "process_assets": [
    {
      "raw": {
        "point_type": "BI",
        "state": false,
        "quality": {
          "online": false,
          "comm_lost": true,
          "restart": false
        }
      },
      "mapped": {
        "found": true,
        "asset_id": "PUMP-2-RUN",
        "intent": "Raw-water pump running feedback"
      }
    }
  ]
}

Useful for: separating an apparent process state from poor data quality. The event preserves both the displayed state and the COMM_LOST evidence.

7. Analog over-range condition

Operational text

RTU-Process → SCADA Master: TANK-LEVEL [Clarifier tank level] = 12.7 m [OVER_RANGE]

Filterable context

{
  "event_type": "unsolicited_event",
  "category": "event",
  "severity": "high",
  "asset_id": "TANK-LEVEL",
  "asset_type": "level_measurement",
  "value": 12.7,
  "unit": "m",
  "critical": true
}

Useful for: presenting measurement value, engineering unit, asset identity, and protocol quality together instead of emitting an unexplained numeric field.

8. Cold restart command

Operational text

SCADA Master sent COLD RESTART to RTU-East

Filterable tags

{
  "event_type": "maintenance_command",
  "category": "maintenance",
  "severity": "critical",
  "operation": "cold_restart"
}

Useful for: immediate OT-SOC review, maintenance-window verification, change audit, and investigation of an unexpected device restart.

9. Configuration change

Operational text

SCADA Master sent Write to RTU-South

Filterable tags

{
  "event_type": "configuration_change",
  "category": "configuration",
  "severity": "medium",
  "operation": "write",
  "function_code": 2,
  "function_name": "Write"
}

Enable or Disable Unsolicited commands use the same configuration category with their own operation names and a default low severity.

Useful for: configuration audit and comparison with approved engineering activity.

10. Unknown outstation and control point

Normalization does not fail when the asset map is incomplete.

Operational text

DNP3 Master (addr 1) sent DIRECT OPERATE: Close to point 17:CROB:0 on DNP3 Outstation (addr 17)

Discovery fields

{
  "event_type": "control_command",
  "category": "control",
  "severity": "high",
  "unknown_dst_device": true,
  "unknown_point": true,
  "dst_device_addr": 17,
  "point_key": "17:CROB:0"
}

Useful for: detecting inventory gaps, newly introduced RTUs, undocumented points, and prioritizing asset-map completion. “Unknown” means absent from the customer map; it is a discovery signal, not by itself proof of malicious activity.

Example dashboard questions

The classification fields make direct operational queries possible:

-- Critical controls by asset
SELECT timestamp, src_device_name, dst_device_name, asset_id, action, description
FROM industrial_events
WHERE category = 'control' AND critical
ORDER BY timestamp DESC;

-- Device-health alarms
SELECT timestamp, src_device_name AS device, severity, description
FROM industrial_events
WHERE alarm AND severity = 'critical'
ORDER BY timestamp DESC;

-- Inventory gaps
SELECT * FROM asset_discovery_report ORDER BY occurrences DESC;
SELECT * FROM point_discovery_report ORDER BY occurrences DESC;

Observed deployment examples

The following patterns were observed in the live PostgreSQL deployment snapshot used to validate this documentation. Repeated rows were grouped so the examples represent operational patterns rather than a packet dump.

Observed industrial text Classification Occurrences
SCADA Master sent SELECT: Latch Off command to F12_BREAKER (feeder_breaker) on North RTU — F12 Feeder Breaker — Latch On=Close, Latch Off=Open control sequence · medium · critical asset 32
SCADA Master sent OPERATE: Latch On command to F12_BREAKER (feeder_breaker) on North RTU — F12 Feeder Breaker — Latch On=Close, Latch Off=Open control command · high · critical asset 4
SCADA Master sent OPERATE: Set=0 command to TR1 (power_transformer) on North RTU — TR1 OLTC tap position setpoint command control command · high 3
SCADA Master sent Enable Unsolicited to North RTU configuration change · low 2
East Recloser → SCADA Master: IIN: DEVICE_RESTART, NEED_TIME unsolicited event · critical · alarm 1
South DER Controller → SCADA Master: IIN: DEVICE_RESTART, NEED_TIME unsolicited event · critical · alarm 1

The same snapshot also contained large periodic responses with mapped breaker, relay, bus-voltage, transformer-loading, temperature, tap-position, and communications-health points plus unmapped points. These demonstrate why dashboards should summarize routine polling while preserving the complete process_assets record for drill-down.

Interpretation boundary

The examples represent output supported by the current event model and DNP3 formatter; exact text depends on decoded fields and the customer's asset map. Severity and text normalization support triage and reporting. They do not replace Rule Engine policy, stateful validation, process interlocks, or the original parsed evidence.