A fully self-contained Python simulation of an industrial PLC ↔ SCADA data acquisition stack, built as a portfolio project demonstrating real-world industrial automation concepts.
┌───────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────┐ Modbus TCP ┌──────────────────────┐ │
│ │ PLC Simulator │◄──────────────►│ SCADA Client │ │
│ │ (pymodbus srvr) │ port 5020 │ (pymodbus client) │ │
│ │ │ │ → sensor_log.csv │ │
│ │ 5 registers: │ └──────────────────────┘ │
│ │ • Temperature │ │
│ │ • Pressure │ Modbus TCP ┌──────────────────────┐ │
│ │ • Pump status │◄──────────────►│ Dash Dashboard │ │
│ │ • Valve status │ port 5020 │ localhost:8050 │ │
│ │ • Flow rate │ │ live charts + alerts│ │
│ └─────────────────┘ └──────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────┘
| Skill | How it's shown |
|---|---|
| Modbus TCP protocol | pymodbus server (slave) + client (master), FC3 holding registers |
| PLC register mapping | Raw integer encoding with scale factors (e.g.275 → 27.5 °C) |
| Industrial data acquisition | 1-second polling loop with error recovery and reconnect |
| SCADA HMI concepts | Real-time dashboard with threshold alerting, status badges, trend charts |
| Data logging | Timestamped CSV with alert column for offline analysis |
| Full-stack OT integration | Field device → protocol layer → visualisation layer |
modbus-scada-demo/
│
├── config.py # ★ Single source of truth: hosts, ports, register map, thresholds
├── plc_simulator.py # Modbus TCP Server — simulates a PLC with 5 live registers
├── scada_client.py # Modbus TCP Client — polls PLC, logs to CSV, prints alerts
├── dashboard.py # Dash web dashboard — live gauges, trend charts, alerts
├── run_demo.py # One-command launcher for all three processes
│
├── data/
│ └── sensor_log.csv # Created at runtime by scada_client.py
│
├── requirements.txt
└── .gitignore
pip install -r requirements.txtpython run_demo.pyThen open http://localhost:8050 in your browser.
Press Ctrl-C once to stop all processes.
# Terminal 1 — start the PLC
python plc_simulator.py
# Terminal 2 — start the data logger (after PLC is running)
python scada_client.py
# Terminal 3 — start the dashboard
python dashboard.pyAll data is stored in Holding Registers (Modbus Function Code 3).
| Address | Tag | Raw encoding | Engineering value | Example |
|---|---|---|---|---|
| 0 | Temperature | int × 0.1 → °C |
20–41 °C | 275 → 27.5 °C |
| 1 | Pressure | int × 0.01 → bar |
1–5 bar | 350 → 3.50 bar |
| 2 | Pump Status | 0 or 1 |
OFF / ON | 1 → Pump running |
| 3 | Valve Status | 0 or 1 |
CLOSED / OPEN | 0 → Valve closed |
| 4 | Flow Rate | int × 0.1 → L/min |
0–18 L/min | 125 → 12.5 L/min |
The PLC simulator advances a physics model every second:
- Temperature — 60-second sine wave:
27 + 14·sin(2π·t/60)+ Gaussian noise - Pressure — 40-second cosine wave:
3 + 2·cos(2π·t/40)+ Gaussian noise - Pump — switches ON when temperature > 32 °C (cooling pump logic)
- Valve — opens when pressure > 3.8 bar (pressure-relief valve logic)
- Flow — proportional to pump state with turbulence noise
This creates natural threshold crossings that let you see the alert system fire without manual intervention.
Defined in config.py — change them freely:
| Tag | Low | High |
|---|---|---|
| Temperature | 15 °C | 38 °C |
| Pressure | 1.5 bar | 5.0 bar |
| Flow Rate | 3.0 L/min | 18.0 L/min |
data/sensor_log.csv is created automatically:
timestamp,temperature_c,pressure_bar,pump_status,valve_status,flow_rate_lpm,alerts
2024-11-01 14:23:01,27.5,3.50,1,0,12.5,OK
2024-11-01 14:23:02,38.9,4.92,1,1,14.1,🚨 HIGH Temperature: 38.9 °C (max 38.0 °C)
Load it in pandas for post-run analysis:
import pandas as pd
df = pd.read_csv("data/sensor_log.csv", parse_dates=["timestamp"])
df.set_index("timestamp", inplace=True)
df[["temperature_c", "pressure_bar"]].plot()All parameters live in config.py:
MODBUS_HOST = "localhost"
MODBUS_PORT = 5020 # change to 502 for a real PLC (needs root on Linux)
MODBUS_SLAVE_ID = 1
THRESHOLDS = {
"temperature": {"low": 15.0, "high": 38.0},
...
}To point the client and dashboard at a real PLC instead of the simulator:
python scada_client.py --host 192.168.1.100 --port 502- pymodbus 3.7 — Modbus TCP server and client
- Dash 2.17 — Reactive web framework for the HMI
- Plotly 5.22 — Interactive charts
- Python 3.10+
MIT
