Industrial IoT

MQTT SCADA: Modernizing Telemetry with Report by Exception

How MQTT changes SCADA telemetry: report by exception instead of polling, a broker as the hub between RTUs and SCADA hosts, state awareness with Last Will and Sparkplug B, and a step-by-step migration path.

Remote RTUs publishing telemetry to a central broker that feeds SCADA servers and dashboards, illustrating an MQTT SCADA architecture
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

MQTT SCADA architectures replace the classic master-polls-every-RTU model with devices that report by exception to a central broker, and SCADA hosts that subscribe to what they need. This guide explains why that shift saves bandwidth and simplifies integration, how state awareness works when nothing is being polled, and how to migrate an existing system step by step. You will also build a small Python host that detects offline and stale RTUs, the core skill behind any MQTT-based SCADA.

How Traditional SCADA Telemetry Works

In a classic SCADA system the master station owns the conversation. It polls each RTU or PLC in turn over Modbus, DNP3 or a vendor protocol, asking for the same registers every cycle whether anything changed or not. The model has real strengths: the master always knows whether a device answered, and timing is predictable.

It also has well-known costs:

  • Bandwidth spent on unchanged data. A tank level that moves twice an hour is still read every few seconds.
  • Scan time grows with the network. On radio or satellite links, adding RTUs lengthens the poll cycle for everyone.
  • Tight coupling. Every new consumer, a historian, an analytics tool, a second control room, either polls the devices again or depends on the SCADA server's interfaces.
  • Device addresses everywhere. Register maps and protocol details leak into every system that needs the data.

What Changes with MQTT SCADA

With MQTT, the RTU or edge gateway opens an outbound connection to a broker and publishes values when they change meaningfully. SCADA hosts, historians and dashboards are subscribers. The broker sits in the middle and fans messages out.

Aspect Poll-response SCADA MQTT SCADA
Who initiates Master polls each device Device publishes, hosts subscribe
Traffic Every point, every scan Only changes, plus heartbeats
Adding a consumer New polls or SCADA interface New subscription, devices untouched
Connection direction Master to device (inbound to field) Device to broker (outbound from field)
Device health Implicit: no reply means problem Explicit: Last Will, birth/death messages
Data context Register addresses Named topics, often ISA-95 or Sparkplug
Link efficiency Poor on high-latency links Good: persistent session, small packets

The biggest conceptual shift is report by exception (RBE). Instead of asking, the field reports. A deadband on each analog value and immediate publishing of every digital state change keeps the data current with a fraction of the traffic. The report-by-exception guide covers deadband sizing in detail.

The Broker as the SCADA Hub

In an MQTT SCADA design the broker becomes the central nervous system. Field devices do not know or care how many hosts consume their data, and hosts do not need to know device addresses. This decoupling enables patterns that are awkward with polling:

  • Redundant SCADA hosts subscribe to the same topics without doubling field traffic.
  • IT consumers such as historians and analytics subscribe in parallel without touching the control system.
  • Phased migrations can run old and new SCADA side by side on the same live data.

Understanding what an MQTT broker does is the foundation here: it routes messages, holds retained values and session state, and enforces who can connect.

State Awareness Without Polling

The most common objection to MQTT SCADA is: "If I stop polling, how do I know an RTU is alive?" MQTT answers this at two levels.

Keep alive and Last Will. Every client declares a keep alive interval. If the broker hears nothing for 1.5 times that interval, it closes the session and publishes the client's Last Will message, for example a retained offline on the RTU's status topic. When the RTU reconnects it publishes a retained online. Every subscriber, including ones that start later, sees the current state.

Sparkplug B. The Sparkplug specification formalizes this for SCADA: edge nodes publish NBIRTH and DBIRTH messages with all metrics on connect, NDEATH as their Last Will, and a primary host publishes its own STATE so edge nodes know whether a SCADA host is listening. Hosts can request a rebirth when they detect a gap. Ignition and many edge gateways implement this natively; see Sparkplug primary host applications for the host side.

Even with plain JSON topics you should also detect stale data: a device that is connected but whose values have stopped updating, for example because its field I/O failed.

Example: A Minimal MQTT SCADA Host in Python

This host subscribes to RTU status and telemetry, tracks online state from retained Last Will messages, and flags any RTU whose data has not changed or refreshed within its expected heartbeat. Run it against the public broker with a simulator, then point it at your own broker.

import json
import time
import uuid
import threading

import paho.mqtt.client as mqtt

BROKER, PORT = "broker.connectmq.com", 1883
PREFIX = "connectmq/demo/scada-7h2k"          # random prefix: public data is readable by all
STALE_AFTER = 120                             # seconds; heartbeat is 60 s

rtus = {}   # rtu_id -> {"online": bool, "last_seen": float, "values": dict}
lock = threading.Lock()


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    client.subscribe(f"{PREFIX}/+/status", qos=1)
    client.subscribe(f"{PREFIX}/+/telemetry/#", qos=1)


def on_message(client, userdata, msg):
    parts = msg.topic.split("/")
    rtu_id = parts[3]
    with lock:
        rtu = rtus.setdefault(rtu_id, {"online": False, "last_seen": 0.0, "values": {}})
        if parts[4] == "status":
            rtu["online"] = msg.payload.decode() == "online"
            print(f"[STATE] {rtu_id} is {'ONLINE' if rtu['online'] else 'OFFLINE'}")
        else:
            point = "/".join(parts[5:])
            rtu["values"][point] = json.loads(msg.payload)
            rtu["last_seen"] = time.time()


def watchdog():
    while True:
        time.sleep(10)
        now = time.time()
        with lock:
            for rtu_id, rtu in rtus.items():
                if rtu["online"] and now - rtu["last_seen"] > STALE_AFTER:
                    print(f"[STALE] {rtu_id}: no data for {int(now - rtu['last_seen'])} s")


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"scada-host-{uuid.uuid4().hex[:6]}")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, PORT, keepalive=30)
threading.Thread(target=watchdog, daemon=True).start()
client.loop_forever()

Simulate an RTU in another terminal by publishing a retained status and one telemetry value:

mosquitto_pub -h broker.connectmq.com -p 1883 -i rtu-sim-7h2k \
  -t 'connectmq/demo/scada-7h2k/rtu07/status' -m online -r -q 1
mosquitto_pub -h broker.connectmq.com -p 1883 -i rtu-sim2-7h2k \
  -t 'connectmq/demo/scada-7h2k/rtu07/telemetry/tank1/level' -m '{"value":3.42,"unit":"m"}' -q 1

For a long-running simulator with a real Last Will, use mosquitto_sub or a script with --will-topic connectmq/demo/scada-7h2k/rtu07/status --will-payload offline --will-retain and kill it to watch the host react. Use the free public MQTT broker only for simulated values like these.

Migrating an Existing SCADA System

A big-bang replacement is rarely necessary. A phased path keeps operations safe:

  1. Mirror first. Add an MQTT publisher next to the existing master, either a SCADA module that forwards tags or an edge gateway that reads the same RTUs in listen-only mode. Nothing in control changes.
  2. Define the namespace. Agree on topic structure (ISA-95 paths or Sparkplug group and node IDs) and payload format before many sources exist.
  3. Connect read-only consumers. Historians, dashboards and analytics move to the broker. Validate values against the SCADA screens.
  4. Convert sites to RBE. Replace polling on one site or link type at a time with RBE-capable RTUs or edge gateways. Compare bandwidth and data freshness.
  5. Move control carefully. Commands over MQTT need confirmation patterns, timeouts and interlocks. Sparkplug's command messages or MQTT 5 request/response with response topics help structure this.
  6. Retire legacy polling only when every consumer has moved and alarm behavior has been verified.

Design Pitfalls

  • No heartbeat. Pure RBE with no periodic refresh makes a frozen value indistinguishable from a quiet one.
  • Device-clock timestamps without sync. Sequence-of-events analysis breaks if RTU clocks drift. Use NTP or GPS time at the source.
  • QoS 2 everywhere. It adds round trips on slow links. QoS 1 with idempotent consumers is the usual choice for telemetry.
  • Retaining everything. Retain status and last values, not high-rate streams.
  • Flat topics. rtu07/ai3 will not scale; name by site and asset.

Where ConnectMQ Fits

A SCADA hub broker needs TLS, per-device credentials and protection against misbehaving clients. ConnectMQ provides MQTT 3.1.1 and 5.0 with TLS, binds each client ID to its issued credentials, and applies flood protection and IP blocking after repeated failed logins. The portal decodes Sparkplug B payloads, can republish Sparkplug data into readable ISA-95 topics, and offers threshold alert rules with an alerts log, which is handy for a quick operations view alongside your SCADA host. SCADA software, historians and scripts connect as ordinary MQTT clients. If you would rather not run broker infrastructure at every control center, a fully managed MQTT service is worth evaluating; for implementation details with a specific SCADA package, see Ignition with MQTT, and for the broader context the industrial IoT with MQTT guide.

Modernize Your SCADA Telemetry with ConnectMQ

Start with one site or one RTU type. Create a free Serverless broker at app.connectmq.com, no credit card required, connect a gateway and your SCADA host, and compare the data side by side with your existing polling.

Frequently Asked Questions

Can MQTT replace DNP3 or Modbus in SCADA?

MQTT often replaces the transport between field sites and the control center, while Modbus or DNP3 may still be used locally between an edge gateway and field devices.

How does an MQTT SCADA host know a device is offline?

Through the device's Last Will message, published by the broker when the keep alive expires, or through Sparkplug NDEATH messages. Add stale-data detection for devices that stay connected but stop updating.

Is Sparkplug B required for MQTT SCADA?

No, but it standardizes birth and death messages, metric definitions and host state, which makes interoperability between SCADA hosts and edge devices much easier.

What QoS should SCADA telemetry use?

QoS 1 is the common choice, combined with retained status topics. QoS 0 suits high-rate data where the next sample supersedes a lost one.

Related articles

IIoT Edge Gateway: What It Does and How to Choose One

An IIoT edge gateway connects machines that speak Modbus, OPC UA or proprietary protocols to MQTT and the cloud. Learn its core roles, common architectures and how to choose the right one.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.