MQTT water utilities telemetry brings scattered pump stations, reservoirs, booster stations and wastewater lift stations into one live data layer that operators, engineers and planning tools can all subscribe to. This guide shows how to structure that telemetry, which signals to publish and how, and how to raise threshold alerts on levels and pressures before a reservoir runs low or a wet well overflows. You will also run a small station simulator and an alarm helper you can adapt to real equipment.
The Telemetry Problem in Water Networks
A typical utility operates dozens to hundreds of remote sites spread across its service area. Many were connected over decades with different RTUs, radios and protocols, and polled by a central SCADA master. That arrangement works, but it creates familiar pain points:
- Slow scans on shared radio. Polling every site in turn means a level alarm may wait for the next scan.
- Data locked in SCADA. Asset management, hydraulic modeling and energy teams request exports or build fragile point-to-point interfaces.
- Mixed generations of equipment. Each new site type adds another driver and another register map.
- Cellular sites everywhere. Newer sites use cellular modems, which favor device-initiated, outbound connections rather than inbound polling.
MQTT reverses the direction: each station publishes its own changes to a broker, and every consumer, including SCADA, subscribes. If you are planning the SCADA side of this shift, read MQTT in SCADA systems alongside this guide.
What to Publish from Each Site
Start from the operational questions, not from the RTU's register list. The table below is a practical baseline for water and wastewater sites.
| Site type | Signal | Unit | Publish rule | Alert idea |
|---|---|---|---|---|
| Reservoir / tank | Level | m | Deadband 2 to 5 cm, heartbeat 15 min | Low and high level |
| Reservoir / tank | Inlet valve position | % | On change of 2 % | Valve stuck while level falls |
| Booster or pump station | Discharge pressure | bar | Deadband 0.05 to 0.1 bar | High and low pressure |
| Booster or pump station | Flow | m3/h | Deadband 2 to 5 % of range | Flow with pump stopped |
| Booster or pump station | Pump run status | bool | Every change | Too many starts per hour |
| Wastewater lift station | Wet well level | m | Deadband 2 cm, faster heartbeat | High level, overflow risk |
| All sites | Mains power, intrusion, comms | bool | Every change, retained | Any change |
| Treatment points | Chlorine residual | mg/L | Deadband 0.02 mg/L | Below or above target band |
Two rules make this table work. Publish analog values by exception with a deadband sized from instrument accuracy, and publish every state change immediately. The report-by-exception guide explains deadband sizing and why a heartbeat is still necessary.
Designing Topics for a Water Network
Topics should mirror how operators think about the network: region, site, asset, measurement. A hierarchy in the spirit of ISA-95 keeps it consistent:
waterco/north/reservoir-hilltop/tank1/level
waterco/north/booster-ridge/pump2/running
waterco/north/booster-ridge/discharge/pressure
waterco/south/lift-station-07/wetwell/level
waterco/south/lift-station-07/site/status
Guidelines that pay off later:
- Keep site and asset names stable and lowercase; they become identifiers in every downstream system.
- Put one measurement per topic, or one small JSON object per asset, and choose one approach utility-wide.
- Give every site a retained
site/statustopic with a Last Will, so a lost cellular connection shows asofflineinstead of frozen values. - Do not encode RTU register numbers in topics. Keep them in a
sourcefield if engineers need them.
When existing SCADA tag names already follow a convention, map them into the new hierarchy in the gateway rather than renaming points in SCADA. Operators keep the names they know, while every new consumer gets clean, predictable topics. Document the mapping once and review it whenever a site is added, because inconsistencies are far cheaper to fix at the second site than at the fiftieth.
Example: Pump Station Simulator
The script below simulates a booster station with a reservoir it feeds: two pumps, discharge pressure and reservoir level. It publishes by exception with heartbeats and a Last Will, which makes it a good stand-in while you design dashboards and alerts. It uses the public MQTT test broker, so it only sends simulated values under a random prefix.
import json
import random
import time
import uuid
import paho.mqtt.client as mqtt
BROKER, PORT = "broker.connectmq.com", 1883 # production: your broker, port 8883 + TLS
CLIENT_ID = f"booster-sim-{uuid.uuid4().hex[:6]}" # production: "YOUR_CLIENT_ID"
BASE = f"connectmq/demo/{uuid.uuid4().hex[:6]}/north/booster-ridge"
HEARTBEAT = 300
def on_connect(client, userdata, flags, reason_code, properties):
print("Connected:", reason_code, "- publishing under", BASE)
client.publish(f"{BASE}/site/status", "online", qos=1, retain=True)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.will_set(f"{BASE}/site/status", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.connect(BROKER, PORT, keepalive=60)
client.loop_start()
level, pumps = 3.2, [False, False]
last = {}
last_full = 0.0
def publish(name, value, deadband=None, unit=None, retain=False):
prev = last.get(name)
if prev is not None and not force:
if deadband is None and value == prev:
return
if deadband is not None and abs(value - prev) < deadband:
return
payload = {"value": value, "ts": int(time.time())}
if unit:
payload["unit"] = unit
client.publish(f"{BASE}/{name}", json.dumps(payload), qos=1, retain=retain)
last[name] = value
while True:
force = time.time() - last_full >= HEARTBEAT
demand = random.uniform(0.010, 0.020)
supply = 0.018 * sum(pumps)
level = round(max(0.0, level + supply - demand), 3)
# Simple level control: start pumps when the reservoir falls, stop when full
if level < 2.8:
pumps = [True, level < 2.4]
elif level > 3.6:
pumps = [False, False]
pressure = round(1.2 + 2.1 * sum(pumps) + random.uniform(-0.03, 0.03), 2)
publish("reservoir/level", level, deadband=0.03, unit="m")
publish("discharge/pressure", pressure, deadband=0.08, unit="bar")
publish("pump1/running", pumps[0], retain=True)
publish("pump2/running", pumps[1], retain=True)
if force:
last_full = time.time()
time.sleep(5)
Watch the station from another terminal:
mosquitto_sub -h broker.connectmq.com -p 1883 -i booster-watch-01 \
-t 'connectmq/demo/+/north/booster-ridge/#' -v
You will see the reservoir slowly fall, pumps start, discharge pressure jump and the level recover, with only a few messages per minute despite a five-second loop.
Threshold Alerts on Levels and Pressures
Telemetry earns its keep when it tells someone about a problem early. On a private ConnectMQ broker you can add threshold alert rules in the portal, such as a reservoir level below 1.5 m or a discharge pressure above 6 bar, and review every triggered alert in the alerts log. That gives engineers a fast, independent view of critical limits during a pilot or alongside SCADA.
Some conditions are not a single threshold. Pump short-cycling, for example, is a count of starts over time. The honest pattern for logic like this is a small subscriber service connected to the broker as an ordinary MQTT client:
import json
import time
from collections import deque
import paho.mqtt.client as mqtt
MAX_STARTS_PER_HOUR = 6
starts = {} # topic -> deque of start timestamps
state = {}
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe("connectmq/demo/+/north/+/+/running", qos=1)
def on_message(client, userdata, msg):
running = json.loads(msg.payload)["value"]
was_running = state.get(msg.topic, running)
state[msg.topic] = running
if running and not was_running:
window = starts.setdefault(msg.topic, deque())
now = time.time()
window.append(now)
while window and now - window[0] > 3600:
window.popleft()
if len(window) > MAX_STARTS_PER_HOUR:
print(f"ALERT short-cycling: {msg.topic} started {len(window)} times in 1 h")
# Forward to your paging, SMS or ticketing tool here.
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"shortcycle-{time.time_ns() % 1000000}")
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_forever()
The same approach works with Node-RED or any other MQTT-capable tool. Notification delivery stays in the tool your operations team already uses.
Security and Reliability Checklist
Water utilities are critical infrastructure, so the bar is higher than for a typical IoT project.
- TLS on port 8883 for every station; never send telemetry in clear text over cellular or public networks.
- A unique username, password and client ID per station, revocable individually.
- Outbound-only connections from stations; no open inbound ports on modems.
- Monitoring separated from control. Commands to pumps and valves stay in SCADA with its interlocks unless a reviewed design says otherwise.
- Retained site status with Last Will, and alerts on unexpected
offline. - Local buffering at stations so short outages do not create gaps in compliance-relevant data.
- Time synchronization at the source for accurate event sequences.
- A documented topic standard, owned by one team, applied to every new site.
Choosing Where the Broker Runs
Utilities with mixed connectivity, some sites on private radio and others on cellular, benefit from a broker reachable from anywhere over TLS. A hosted MQTT broker removes patching and certificate renewals from the utility's workload. ConnectMQ provides MQTT 3.1.1 and 5.0, an ISA-95 devices view that groups sites and assets, and Dedicated or BYOC options for utilities that need an isolated cluster or deployment in their own cloud account. For architecture choices between station PLCs, gateways and SCADA forwarding, see PLC to cloud connectivity; for cellular modem behavior, MQTT over cellular. The industrial IoT with MQTT guide ties these pieces together.
Start a Water Telemetry Pilot with ConnectMQ
Pick one reservoir and one pump station, connect them to a cloud MQTT broker, and set alerts on the levels and pressures that matter most. Create a free Serverless broker at app.connectmq.com, no credit card required, and have live station data in the topic explorer today.
Frequently Asked Questions
Can MQTT replace our water SCADA system?
MQTT usually replaces the telemetry transport, not the SCADA functions. SCADA subscribes to the broker for operations, while other teams consume the same data independently.
How do we know a pump station has lost communications?
Give each station a Last Will on a retained status topic. When its keep alive expires, the broker publishes offline, and subscribers or alert rules react immediately.
What deadband should we use for reservoir levels?
A few centimeters is typical, based on instrument accuracy and how fast the level moves. Combine it with a periodic heartbeat so a steady level still proves the station is alive.
Is MQTT secure enough for critical water infrastructure?
MQTT with TLS, per-station credentials, outbound-only connections and separation of monitoring from control is a strong baseline. Pair it with your utility's wider cybersecurity program.



