Real-time power generation telemetry
I built the service that reads five energy meters every 2 seconds, integrates energy per hourly period, compares it against the market dispatch and feeds a live dashboard and a Power BI report.
Project facts
- Role
- Data and backend engineer, sole developer
- Period
- Mar 2026 to present
- Public links
- Internal system: the code is private, and names, data and diagrams were left out or recreated.
2 s
read interval across 5 meters over Modbus TCP, with 15 to 25 ms p50 latencySource: Service configuration and latency measurements, 2026 300 MB to 5 MB
smaller deployment after moving from an automated browser to reading the meters directlySource: Project architecture decision log 410+
automated test cases across Vitest and pytestSource: Repository test suites, September 2026
The problem
Operations needed to see, on a single screen, the actual output of four generating units against the market operator's dispatch, and the existing readings depended on a centralized system that was a single point of failure.
My role
- Designed direct meter reading, per-period energy integration and the comparison against market dispatch.
- Built the Node.js backend, the React dashboard and the Python service that feeds the data lakehouse.
- Statistically validated the backup meters and documented the sign convention of each metering point.
- Run two production instances, one per plant, from the same build.
Constraints
- Readings every 2 seconds without losing integrated energy if the service restarts.
- Two plants with different sign conventions depending on the metering point.
- A failed meter must not pollute the totals with false zeros.
Decisions
- 01
Read the meters directly over Modbus TCP
Removes the single point of failure and the automated browser from the deployment, and makes the latency of every reading measurable.
Ruled out
- Keep scraping the centralized system's web interface
- 02
Null means no reading, never zero
Telling missing data apart from a zero value makes a failed meter detectable instead of reporting zero generation. The rule lives in a domain module shared by the backend and the dashboard.
Ruled out
- Fill missing readings with zero
- 03
Statistical validation before using the backup meters
A one-hour parallel capture with bias, p95 and integrated energy criteria proved the backup meters are interchangeable.
Ruled out
- Assume the backup meters read the same
- 04
One build for two plants, configured at load time
Each plant's branding and unit order are read from a configuration file at runtime, so a fix ships once and serves both.
Ruled out
- Compile a separate dashboard per plant
Architecture
Sources
- Energy meters5 meters, Modbus TCP every 2 s
- Market operator filesDaily dispatch and redispatch every 5 min
- Redispatch emailsRead with Microsoft Graph
Processing
- Meter readerFailures isolated per unit
- Energy integratorMW to MWh per hourly period
- Python telemetry serviceDelta tables without Spark
Storage
- SQL ServerPeriods, dispatch and change history
- Microsoft Fabric lakehouseMirrored to PostgreSQL
Consumers
- Live dashboardReact over WebSockets
- Power BI report
How data moves
- Energy meterstoMeter readerReadings every 2 s
- Meter readertoEnergy integratorPower per unit, null if a meter is missing
- Market operator filestoEnergy integratorScheduled dispatch per period
- Redispatch emailstoEnergy integratorFinal dispatch for the period
- Energy integratortoSQL ServerCheckpoint every 30 s
- Energy integratortoLive dashboardOutput, projection and deviation
- Energy meterstoPython telemetry serviceIndependent reading
- Python telemetry servicetoMicrosoft Fabric lakehouseWrite every 15 s
- Microsoft Fabric lakehousetoPower BI reportCorporate report model
Results
- Operations sees on one screen the actual output of four units against dispatch, with a projection for the current period.
- The single point of failure is gone and read latency dropped from about 1.1 s to 15 to 25 ms (p50).
- After a restart, the service rebuilds closed periods from the database and loses no integrated energy.
- The same telemetry feeds a corporate Power BI report without touching the dashboard.
What I would do differently
- I would measure latency and the rate of null readings from day one; those figures justified the protocol change and would have brought the decision forward.
- I would keep a single meter-reading service that publishes to both consumers, instead of maintaining the same topology in Node.js and in Python.
Technologies
- Node.js
- Python
- Modbus TCP
- WebSockets
- SQL Server
- Microsoft Fabric
- PostgreSQL
- Power BI
- Microsoft Graph
- React
- Linux
- systemd