Water Treatment Plant Simulator in Ignition
A Python OPC UA server simulating a full water treatment plant, wired into Ignition SCADA for dashboards, trends, and alarms.
بسم الله الرحمن الرحيم
I've been getting deeper into SCADA at work, and Ignition by Inductive Automation kept coming up as the platform I should actually be spending time in. It's what a lot of the newer sites we support are running, and it has a reputation for being fast to prototype in once you understand its shape. The problem was that the fastest way to learn Ignition is to have live data flowing through it, and I didn't have a plant I could plug into. So I built one in Python.
The simulator
The simulator is an OPC UA server written in Python. It exposes a tag for every meaningful process variable across the plant: raw water intake flow, filter pressure differentials, chemical dosing setpoints, tank levels, distribution pump statuses, the works. Each tag updates a few times per second with realistic drift, so from Ignition's side it looks and feels like a live plant.
Here's the drift function:
def simulate_realistic(prev, target_range, max_change):
target = uniform(*target_range)
delta = target - prev
change = max(min(delta, max_change), -max_change)
return prev + change
# Example: intake flow with a realistic range and step limit
vals["raw_flow"] = simulate_realistic(vals["raw_flow"], (160, 200), 1.5)
raw_flow.set_value(round(vals["raw_flow"], 2))Each tick picks a random target inside a plausible range and steps toward it, clamped so the value can't jump more than max_change per cycle. The result on a trend chart reads like a real sensor with realistic inertia, not a random walk.
What the sim covers
Every stage of a typical water treatment process has its own set of tags:
Raw water intake — flow rate, source temperature, turbidity.
Filtration — inlet and outlet pressures, backwash valve status, filter run time.
Chemical treatment — chlorine concentration, pH, dosing pump statuses, tank levels.
Clean water storage — tank level, temperature, overflow alarm.
Distribution — pump statuses, zone pressures, backup generator status.
There are also a handful of interlocks so the tags aren't fully independent. If the raw intake flow drops, the chemical dosing scales down proportionally. If the storage tank overflow alarm fires, the distribution pumps shed load. Small touches, but they make the Perspective screens feel more coherent because the values move together when they should.
The Ignition side
Ignition's Perspective module is where I built the operator screens. The main screen gives a plant-wide overview: flows, tank levels, pump statuses, alarm summary. Individual tabs drill into each stage of the process. Clicking any tag pulls up its historical trend and diagnostic info through Ignition's built-in charting.
Getting the tags into Ignition was the easy part. OPC UA is a first-class citizen in Ignition, so once the Python server was running I pointed the OPC UA connector at it, imported the tag tree, and everything showed up. From there it's data binding, styling, and the occasional Python scripting on the Perspective side for computed values.
Next steps
The sim is a foundation, not a finished project. I want to add:
Realistic alarm scenarios that unfold over time. A chlorine dosing pump fails, downstream pH climbs, operator has to acknowledge.
Recipe-driven changes so I can run "shift change" or "backwash cycle" scenarios on demand.
Historian configuration on the Ignition side so I can practice building historical trend dashboards on real historized data.
Longer term, I want to turn this into a training environment for junior operators. The Python side is small enough to be portable, and the Ignition side is easy to hand off with a Gateway backup file for someone else to restore.
Salaam.