SCADA Stack
Bright Theme · Mobile Friendly · Layered Stack · Real Examples

SCADA Water Automation Development Stack

A standalone learning page that visualizes SCADA as layers of development: physical assets, instrumentation, control logic, communications, historian data, applications, cybersecurity, and water operations.

8Development layers
5Maturity phases
30+Terms and examples
One-page mental model
1
Field RealityPumps, tanks, valves, sensors, chemicals, pressure, flow
2
Control SystemPLC / RTU logic, interlocks, protocols, network
3
Operational IntelligenceHMI, historian, alarms, trends, security, response
Main Visualization

SCADA Development Stack

Each layer depends on the layer below it. If the lower layers are weak, everything above becomes unreliable, even if the interface looks modern.

Physical Asset Layer

This is the actual water system. If this layer is misunderstood, the rest of the SCADA design becomes fantasy.

Water example

A pump station pushes water into a pressure zone. A ground storage tank level controls pump start/stop logic. A chlorine analyzer confirms residual after chemical dosing.

PumpsTanksValvesMetersChemicals
Build Order

SCADA Maturity Roadmap

This is the development sequence. Skipping to AI or dashboards before getting tags, quality, alarms, and security right is immature engineering.

Development phases
1

Foundation

Understand assets, instrumentation, I/O, PLCs, RTUs, and control narratives.

  • Equipment list
  • I/O list
  • Control narrative
2

Connectivity

Connect field sites through reliable, segmented, and monitored communications.

  • Network map
  • Protocol selection
  • Failover path
3

Visibility

Build clean HMI screens, alarm priorities, historian tags, and trends.

  • HMI screens
  • Alarm philosophy
  • Historian model
4

Reliability

Improve data quality, redundancy, cybersecurity, auditing, and operating procedures.

  • Quality codes
  • Audit logs
  • Backups
5

Optimization

Use historian trends, alarms, and operations context for smarter decisions.

  • Analytics
  • Predictive alerts
  • Decision support
Architecture

SCADA System Architecture

The control system is not just a screen. It is a chain from field equipment to control logic to network to operator action.

End-to-end architecture flow
EQ
EquipmentPumps, valves, tanks, generators, analyzers
I/O
InstrumentationDI, DO, AI, AO, sensors, transmitters
PLC
PLC / RTULogic, interlocks, sequencing, telemetry
NET
NetworkFiber, radio, cellular, Ethernet, VPN
HMI
SCADA / HMIOperator graphics, commands, alarms
HIS
HistorianTags, trends, events, reports

PLC Logic Example

This simplified structured text shows how a pump starts and stops using tank level, permissives, and a fail-to-start alarm.

PermissiveInterlockSetpointAlarm
Tank Level Pump ControlIEC 61131-3 ST
(* Pump starts when tank is low and all permissives are true *)
Pump1_Permissive :=
    Auto_Mode
    AND NOT EStop_Active
    AND NOT Low_Suction_Pressure
    AND NOT Motor_Overload
    AND NOT Pump1_Fault;

IF Tank_Level_FT <= Low_Start_Level_FT AND Pump1_Permissive THEN
    Pump1_Start_Cmd := TRUE;
END_IF;

IF Tank_Level_FT >= High_Stop_Level_FT OR NOT Pump1_Permissive THEN
    Pump1_Start_Cmd := FALSE;
END_IF;

IF Pump1_Start_Cmd AND NOT Pump1_Run_FB THEN
    TON_FailStart(IN := TRUE, PT := T#10S);
ELSE
    TON_FailStart(IN := FALSE);
END_IF;

Pump1_Fail_To_Start_Alarm := TON_FailStart.Q;
Protocols

Protocol Layer

Protocols are the languages used between devices and systems. Pick based on purpose, not trendiness.

Where protocols fit
MB
ModbusMeter/VFD/analyzer register communication
DNP
DNP3Remote utility telemetry with events
OPC
OPC UAStructured industrial integration
MQTT
MQTT SparkplugPublish/subscribe telemetry with structure
API
REST / APIReports, apps, dashboards, external access
ProtocolUse It ForWater ExampleDo Not Ignore
ModbusSimple field device reads/writesRead flow meter registers into PLCRegister map, scaling, security
DNP3Remote utility telemetryRemote tank RTU sends level event to SCADATime sync, event classes, polling strategy
OPC UAStructured integrationSCADA publishes pressure tags to historianCertificates, namespace design, access control
MQTT SparkplugModern distributed telemetryRemote site publishes pump status to brokerTopic discipline, birth/death messages, QoS
REST APIApps, reporting, non-control integrationDashboard queries daily pump runtimeNever use API as direct PLC control path
Historian

Data & Historian Layer

Historian data should capture not only value, but trust. Tag, timestamp, value, and quality are the minimum foundation.

Historian data model
Tag
TagNamed point like PS101_FLOW_MGD
Time
TimestampWhen the value was recorded
Val
ValueNumber, boolean, text, status
QC
QualityGood, bad, uncertain, stale
Evt
EventAlarm, start, stop, acknowledge
Historian SchemaSQL
CREATE TABLE scada_tag (
    tag_id BIGSERIAL PRIMARY KEY,
    tag_name TEXT UNIQUE NOT NULL,
    site_id TEXT NOT NULL,
    equipment_id TEXT,
    data_type TEXT NOT NULL,
    unit TEXT,
    description TEXT,
    is_active BOOLEAN DEFAULT TRUE
);

CREATE TABLE historian_value (
    tag_id BIGINT REFERENCES scada_tag(tag_id),
    ts TIMESTAMPTZ NOT NULL,
    value_num DOUBLE PRECISION,
    value_text TEXT,
    quality_code TEXT NOT NULL DEFAULT 'GOOD',
    source TEXT,
    PRIMARY KEY (tag_id, ts)
);
Trend QuerySQL
SELECT
    date_trunc('hour', hv.ts) AS hour_bucket,
    AVG(hv.value_num) AS avg_pressure_psi,
    MIN(hv.value_num) AS min_pressure_psi,
    MAX(hv.value_num) AS max_pressure_psi
FROM historian_value hv
JOIN scada_tag t ON t.tag_id = hv.tag_id
WHERE t.tag_name = 'PS101_DISCHARGE_PRESSURE_PSI'
  AND hv.quality_code = 'GOOD'
  AND hv.ts > NOW() - INTERVAL '24 hours'
GROUP BY hour_bucket
ORDER BY hour_bucket;
Security

Cybersecurity Layer

Cybersecurity is a cross-cutting layer. It protects every layer underneath it, especially the PLC/RTU control layer.

Safe access stack
WWW
InternetNo direct PLC exposure
MFA
VPN + MFAIdentity and secure access
DMZ
Firewall / DMZControlled traffic boundary
JMP
Jump ServerAudited remote support
SCADA
SCADA ZoneHMI, historian, alarm server
ENG
EngineeringPLC tools and approved changes
PLC
Control NetworkPLC, RTU, I/O, field equipment
Firewall Policy ConceptPolicy
# Bad rule
ALLOW ANY FROM IT_NETWORK TO PLC_NETWORK

# Better rule concept
ALLOW TCP 443 FROM VPN_USERS TO JUMP_SERVER
ALLOW RDP FROM JUMP_SERVER TO ENGINEERING_WORKSTATION
ALLOW OPCUA 4840 FROM SCADA_SERVER TO OPCUA_SERVER
DENY  ANY FROM INTERNET TO PLC_NETWORK
DENY  ANY FROM IT_NETWORK TO PLC_NETWORK
LOG   ALL DENIED TRAFFIC
Audit Log TableSQL
CREATE TABLE scada_audit_log (
    audit_id BIGSERIAL PRIMARY KEY,
    event_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    username TEXT NOT NULL,
    source_ip INET,
    action TEXT NOT NULL,
    asset TEXT,
    old_value TEXT,
    new_value TEXT,
    result TEXT NOT NULL,
    notes TEXT
);
Water Context

Water Operations Layer

Every tag and alarm should connect back to a physical operating consequence: pressure, storage, flow, chemical residual, equipment health, or customer impact.

Drinking water operating system
SRC
SourceRaw water, well, interconnect
TRT
TreatmentDisinfection, filtration, chemicals
PS
Pump StationPumps, VFDs, pressure, flow
TNK
TankStorage, level, residual
ZON
Pressure ZonePRV, demand, service pressure
DST
DistributionMains, valves, hydrants, customers
OPS
ResponseAlarms, operators, crews, SOPs
CriticalLow pressure, chemical feed failure, major pump failure, cyber incident.
HighLow tank level, pump failed to start, generator running, high pressure.
MediumCommunication failure, analyzer fault, flow meter issue, abnormal trend.
LowMaintenance notice, informational event, non-critical status change.
Reference

Terms, Definitions, and Water Examples

Use this glossary as the vocabulary layer. Search by term or filter by category.

Field

Pump Station

A facility that uses pumps to move water or maintain pressure.

Example: Booster pump station increases pressure for a high-elevation zone.

Field

Storage Tank

A water storage asset used for pressure stability, demand balancing, and emergency reserve.

Example: SCADA starts pumps when tank level falls below 14 ft.

Field

VFD

Variable Frequency Drive. Controls motor speed instead of simply on/off operation.

Example: VFD adjusts pump speed to maintain 65 PSI.

Field

PRV

Pressure Reducing Valve. Controls downstream pressure in a pressure zone.

Example: PRV holds downstream pressure near 55 PSI.

Control

PLC

Programmable Logic Controller. The local controller that runs equipment logic.

Example: PLC starts Pump 1 if tank level is low and permissives are true.

Control

RTU

Remote Terminal Unit. Field controller designed for remote telemetry and monitoring.

Example: Remote tank RTU reports tank level over cellular network.

Control

DI

Digital Input. A discrete signal read by a PLC, usually on/off.

Example: Pump run feedback contact is ON or OFF.

Control

DO

Digital Output. A PLC output used to command a discrete action.

Example: PLC energizes a relay to start Pump 1.

Control

AI

Analog Input. A continuous signal such as 4-20 mA representing a measurement.

Example: Tank level transmitter sends 4-20 mA to PLC.

Control

AO

Analog Output. A continuous command signal from PLC to equipment.

Example: PLC sends speed command to VFD.

Control

Permissive

A condition that must be true before equipment is allowed to operate.

Example: Pump can start only if suction pressure is adequate.

Control

Interlock

A protective logic condition that prevents or stops unsafe operation.

Example: Stop pump on motor overload or emergency stop.

Control

Lead/Lag

A pump sequencing strategy where one pump leads and another supports or alternates.

Example: Pump 1 starts first, Pump 2 starts if pressure keeps dropping.

Control

Setpoint

A target or threshold used by control logic.

Example: Start pump at 14 ft tank level and stop at 22 ft.

Control

Deadband / Hysteresis

A buffer zone that prevents rapid on/off switching or alarm chattering.

Example: Start pump at 14 ft, but do not stop until 22 ft.

Protocol

Modbus

A simple protocol for reading and writing device registers.

Example: PLC reads flow meter holding register 40001.

Protocol

DNP3

Utility telemetry protocol designed for remote sites and event reporting.

Example: RTU sends timestamped high tank level event to SCADA.

Protocol

OPC UA

A structured industrial data exchange standard used for system integration.

Example: Historian reads pressure tags from OPC UA server.

Protocol

MQTT Sparkplug

Industrial publish/subscribe messaging with structured device state.

Example: Remote pump station publishes telemetry to a broker.

Data

Tag

A named data point in SCADA or historian.

Example: PS101_DISCHARGE_PRESSURE_PSI.

Data

Quality Code

A flag indicating whether a value should be trusted.

Example: Tank level = 18.4 ft with BAD quality should not be trusted.

Data

Historian

A time-series database for SCADA values, trends, events, and alarms.

Example: Review pressure trend during a main break.

Data

Alarm Event

A record of abnormal condition, acknowledgement, and return-to-normal.

Example: Low pressure alarm active at 2:15 AM, acknowledged at 2:18 AM.

Security

OT / IT Separation

Segmentation between operational technology and business IT systems.

Example: Office laptops cannot directly access PLC network.

Security

DMZ

A controlled network zone between IT and OT networks.

Example: Reporting server sits in DMZ, not directly inside PLC network.

Security

Jump Server

A controlled server used as the approved entry point into a protected network.

Example: Vendor must access engineering workstation through jump server.

Security

MFA

Multi-factor authentication. Requires more than a password.

Example: VPN login requires password plus authenticator approval.

Operations

Alarm Philosophy

A written approach defining alarm priority, response, suppression, and escalation.

Example: Low chlorine residual is critical, meter maintenance notice is low.

Operations

SOP

Standard Operating Procedure. Defines what operators should do for known scenarios.

Example: Low pressure SOP checks pump status, tank level, flow spike, and field confirmation.

Operations

Abnormal Situation

A condition where the system is outside normal operation and needs active attention.

Example: Sudden tank drop, low pressure, high flow, and pump failure at same time.

Full Code Examples

Code Library by Stack Layer

These are practical teaching examples. They show structure and thinking. Production SCADA code must be reviewed by qualified controls, cybersecurity, and operations staff.

Layer 1: Equipment ListJSON
{
  "site_id": "PS101",
  "site_type": "booster_pump_station",
  "equipment": [
    {"id": "PUMP_1", "type": "pump", "motor_hp": 150, "vfd": true},
    {"id": "PUMP_2", "type": "pump", "motor_hp": 150, "vfd": true},
    {"id": "FLOW_METER_1", "type": "mag_meter", "unit": "MGD"},
    {"id": "PRESSURE_TX_1", "type": "pressure_transmitter", "unit": "PSI"}
  ]
}
Layer 2: I/O ListCSV
point_name,io_type,signal,unit,equipment,description
PUMP1_RUN_FB,DI,24VDC,,PUMP_1,Pump 1 run feedback
PUMP1_START_CMD,DO,24VDC,,PUMP_1,Pump 1 start command
DISCH_PRESSURE,AI,4-20mA,PSI,HEADER,Discharge header pressure
PUMP1_SPEED_CMD,AO,4-20mA,%,PUMP_1,VFD speed command
Layer 3: Lead/Lag Pump LogicStructured Text
(* Simplified lead/lag pressure control *)
LowPressure := Discharge_Pressure_PSI < Low_Pressure_SP;
VeryLowPressure := Discharge_Pressure_PSI < Very_Low_Pressure_SP;

IF Auto_Mode AND LowPressure THEN
    LeadPump_Start := TRUE;
END_IF;

IF Auto_Mode AND VeryLowPressure THEN
    LagPump_Start := TRUE;
END_IF;

IF Discharge_Pressure_PSI > Stop_Pressure_SP THEN
    LeadPump_Start := FALSE;
    LagPump_Start := FALSE;
END_IF;
Layer 4: Modbus ReadPython
from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient("10.10.20.15", port=502, timeout=3)
client.connect()

result = client.read_holding_registers(address=0, count=2, slave=1)
if result.isError():
    raise RuntimeError("Modbus read failed")

flow_mgd = result.registers[0] / 100.0
pressure_psi = result.registers[1] / 10.0
print({"flow_mgd": flow_mgd, "pressure_psi": pressure_psi})

client.close()
Layer 4: MQTT Telemetry PublishPython
import json, time
import paho.mqtt.client as mqtt

client = mqtt.Client(client_id="ps101_rtu")
client.connect("10.10.50.20", 1883, 60)

payload = {
    "timestamp": int(time.time() * 1000),
    "metrics": [
        {"name": "Pump1_Run", "value": True, "type": "Boolean"},
        {"name": "Discharge_Pressure_PSI", "value": 64.2, "type": "Float"},
        {"name": "Flow_MGD", "value": 3.18, "type": "Float"}
    ]
}

client.publish("spBv1.0/water/DDATA/PS101/RTU01", json.dumps(payload), qos=1)
client.disconnect()
Layer 4: OPC UA ReadPython
from opcua import Client

client = Client("opc.tcp://10.10.30.12:4840")
client.connect()

try:
    pressure_node = client.get_node("ns=2;s=PS101.DischargePressurePSI")
    pressure_psi = pressure_node.get_value()
    print("Pressure:", pressure_psi)
finally:
    client.disconnect()
Layer 5: Tag ModelJSON
{
  "tag_name": "PS101_DISCHARGE_PRESSURE_PSI",
  "site_id": "PS101",
  "equipment_id": "DISCHARGE_HEADER",
  "data_type": "float",
  "unit": "psi",
  "source_protocol": "OPC_UA",
  "quality_code": "GOOD",
  "alarm_limits": {
    "low_low": 35,
    "low": 45,
    "high": 95,
    "high_high": 110
  }
}
Layer 5: Historian TablesSQL
CREATE TABLE scada_tag (
    tag_id BIGSERIAL PRIMARY KEY,
    tag_name TEXT UNIQUE NOT NULL,
    site_id TEXT NOT NULL,
    equipment_id TEXT,
    unit TEXT,
    description TEXT
);

CREATE TABLE historian_value (
    tag_id BIGINT REFERENCES scada_tag(tag_id),
    ts TIMESTAMPTZ NOT NULL,
    value_num DOUBLE PRECISION,
    value_text TEXT,
    quality_code TEXT NOT NULL,
    source TEXT,
    PRIMARY KEY (tag_id, ts)
);
Layer 6: Simple HMI CardHTML/CSS
<div class="status-card normal">
  <div class="label">Pump 1</div>
  <div class="value">RUNNING</div>
  <div class="meta">64.2 PSI · 3.18 MGD · Quality: GOOD</div>
</div>

<style>
.status-card { padding: 14px; border-radius: 14px; border: 1px solid #ddd; }
.status-card.normal { background: #f0fdf4; color: #166534; }
.status-card.alarm { background: #fef2f2; color: #991b1b; }
.label { font-size: 12px; opacity: .75; }
.value { font-size: 22px; font-weight: 800; }
.meta { font-size: 12px; margin-top: 4px; }
</style>
Layer 6: Alarm EvaluationPython
def evaluate_pressure_alarm(pressure_psi: float, quality: str) -> dict:
    if quality != "GOOD":
        return {"active": True, "priority": "HIGH", "alarm": "PRESSURE_DATA_BAD"}

    if pressure_psi < 35:
        return {"active": True, "priority": "CRITICAL", "alarm": "LOW_PRESSURE_CRITICAL"}

    if pressure_psi < 45:
        return {"active": True, "priority": "HIGH", "alarm": "LOW_PRESSURE_WARNING"}

    return {"active": False}
Layer 7: Audit LogSQL
INSERT INTO scada_audit_log
(username, source_ip, action, asset, old_value, new_value, result)
VALUES
('operator01', '10.20.5.44', 'SETPOINT_CHANGE',
 'GST202_LOW_LEVEL_SP', '14.0', '13.5', 'SUCCESS');
Layer 8: Emergency Response ObjectJSON
{
  "event_type": "LOW_PRESSURE",
  "first_questions": [
    "Which pressure zone is affected?",
    "Are pumps running and available?",
    "Are tanks dropping?",
    "Is there abnormal high flow?",
    "Is the data quality good or stale?"
  ],
  "operator_actions": [
    "Verify SCADA alarm and historian trend",
    "Check pump station and tank status",
    "Contact field crew for confirmation",
    "Escalate if public health or service risk exists"
  ]
}