Full-stack SCADA device management and protocol bridging for renewable energy plants, using MQTT and the Sparkplug B JSON protocol.
Beta software. This project is under active development and is not yet production-ready. APIs, data models, and configuration may change between releases. Contributions and feedback are welcome.
Always-on service. This module is designed to run as a persistent, independent stack — separate from the core API platform. In production it is managed by Systemd Quadlet units (rootless Podman), which handle automatic startup on boot and container restarts without manual intervention.
The SCADA Device Emulation and Bridge API serves two primary roles:
- Device Emulation and Simulation: Runs a multi-process, physics-based fleet launcher that simulates real-world telemetry (turbines, solar arrays, BESS batteries, and weather stations) publishing Sparkplug B JSON payloads over an MQTT broker.
- SCADA Protocol Bridging: Runs an ingestor worker that subscribes to raw MQTT data streams, resolves device-to-plant mappings, standardizes tag naming, and forwards telemetry to the downstream core platform (renewables-api).
- Astro Web UI (
frontend/): An operator dashboard (running on port4322) to manage simulated devices, dynamically inspect telemetry, and configure metric simulator behaviors. - FastAPI Bridge API (
backend/): Provides REST endpoints (running on port8001) to manage the SQLite device registry (data/scada.db) and query the in-memory MQTT LKV (Last Known Value) cache. - Fleet Launcher (
simulator/): A Python orchestration script that spawns independent async simulator processes for each configured device, mapping weather station datasets and dynamic mathematical functions onto telemetry tags. - Ingestor Worker (
scada_ingestor): A lightweight worker process that bridges MQTT streams to the central SCADA database by routing messages based on name prefixes.
- Sparkplug B over MQTT: All emulated devices publish Sparkplug B JSON payloads (
NBIRTH,NDATA,NDEATH), providing a standardised device lifecycle and telemetry model. - Python Fleet Launcher: The fleet launcher (
simulator/fleet_launcher.py) spawns one lightweight Python process per emulated device. It polls the API every 10 seconds and picks up new or deleted devices without a restart. - Dynamic Metric Configuration: Metric definitions are stored in the
device_metricsDB table. On first access the backend seeds per-type defaults (allstrategy=physics). You can change any metric tosine,random,ramp, orconstantvia the API; the simulator picks this up at next startup. - Ingestor & Forwarding: The
scada_ingestorworker subscribes to MQTT streams, resolves plant mapping (e.g.WT_→ Wind Plant), and forwards structured payloads to the downstream API.
Frontend (Astro) ──HTTP──▶ Backend (FastAPI)
│
▼
SQLite DB (devices, device_metrics, mappings)
./data/weather/ (shared weather CSV)
MQTT Broker (Mosquitto) ◀── Sparkplug B JSON ──▶ Fleet Launcher
│ (N × Python processes)
└──────────▶ Ingestor (MqttAdapter) ──▶ renewables-api (TimescaleDB)
renewables-scada-mqtt/
├── backend/
│ ├── scripts/ # Utility scripts (seeding, etc.)
│ └── src/
│ ├── domain/devices/ # Device + DeviceMetric models, per-type defaults
│ ├── infrastructure/
│ │ ├── connectors/ # MqttAdapter, MQTT LKV Cache
│ │ └── persistence/ # SQLAlchemy/SQLite
│ └── infrastructure/worker/ # Ingestor (MQTT → HTTP forwarding)
├── frontend/ # Astro UI (Dashboard, Device Registry, Browse)
├── simulator/
│ ├── fleet_launcher.py # Spawns per-device processes
│ └── src/ # Physics models + DynamicMetricLoader
├── Ingestor.Dockerfile # Ingestor container build
├── docker-compose.yml # Broker, API, UI, Ingestor
└── manage.py # Orchestration script
- Podman and
podman-compose - Python 3.12+
# Production mode — starts infrastructure + fleet launcher
python3 manage.py up
# Dev mode — bind-mounts source, hot-reloads API and frontend, runs fleet launcher
python3 manage.py devup and dev both start infrastructure in the background then run the fleet launcher in the foreground. Ctrl+C stops the simulators; manage.py down stops the containers.
# Seed mock sites and stations
PYTHONPATH=. python3 backend/scripts/seed_mock_data.py --clearpython3 manage.py build # Rebuild backend/frontend images after code changes
python3 manage.py fleet # Fleet launcher only (infrastructure must already be running)
python3 manage.py down # Stop and remove containers
python3 manage.py status # Show container status| Service | URL |
|---|---|
| Management UI | http://localhost:4322 |
| Bridge API (Swagger) | http://localhost:8001/docs |
| MQTT Broker | localhost:1883 |
| Variable | Default | Description |
|---|---|---|
SCADA_INTAKE_URL |
mqtt_broker:1883 |
MQTT broker address |
SCADA_INTAKE_PLANT_ID |
1 |
Fallback plant ID when no prefix matches |
SCADA_INTAKE_FORWARD_URL |
(empty) | Downstream renewables-api endpoint; empty = discard |
PLANT_ID_WIND |
SCADA_INTAKE_PLANT_ID |
Plant ID for WT_ devices |
PLANT_ID_SOLAR |
SCADA_INTAKE_PLANT_ID |
Plant ID for INV_ devices |
PLANT_ID_WEATHER |
SCADA_INTAKE_PLANT_ID |
Plant ID for WX_ devices |
PLANT_ID_BESS |
SCADA_INTAKE_PLANT_ID |
Plant ID for BESS_ devices |
| Document | Description |
|---|---|
| renewables-api | The companion platform that ingests, stores, and visualizes telemetry from this bridge. Refer to that repo for fleet management details, metric strategies, and adding new device types. |
| renewables-db | TimescaleDB + Redis infrastructure required by renewables-api. Must be running before the ingestor can forward telemetry. |