IoT Energy Monitoring System using ESP32 DevKit V1
Track real-time power consumption of household appliances, analyze energy usage, calculate cost, and receive overload alerts — all from a smart web dashboard and OLED display.
📌 Project Overview
This Smart Energy Monitoring System uses the WiFi-enabled ESP32 microcontroller to monitor voltage and current across three channels (Living Room, Kitchen, Bedroom) and calculate real-time power and energy usage. It combines IoT technology with embedded systems to give you full visibility into your home's electricity consumption.
🔬 Working Principle & Formulas
The ESP32 reads analog signals from potentiometers (simulating sensors) via its ADC pins. These raw values are mapped to physical units. The system then applies electrical formulas to derive power, energy, and cost.
Simulation Note
In Wokwi simulation, potentiometers simulate voltage (0–250V) and current (0–20A) sensors. In real hardware, use PZEM-004T or a CT Sensor for AC measurement.
🧰 Components Required
| # | Component | Quantity | Notes |
|---|---|---|---|
| 1 | ESP32 DevKit V1 | 1 | Main microcontroller with WiFi |
| 2 | Potentiometer (Wokwi) / PZEM-004T | 4 | Simulates voltage + 3 currents |
| 3 | CT Sensor (Current Transformer) | 3 | For real AC current measurement |
| 4 | SSD1306 OLED Display (128×64) | 1 | I2C, address 0x3C |
| 5 | LED — Green | 1 | Living Room channel indicator |
| 6 | LED — Blue | 1 | Kitchen channel indicator |
| 7 | LED — Yellow | 1 | Bedroom channel indicator |
| 8 | LED — White | 1 | Power indicator |
| 9 | LED — Red | 1 | Alert indicator |
| 10 | Resistors 220Ω | 5 | Current limiting for LEDs |
| 11 | Buzzer (Active) | 1 | Overload alert sound |
| 12 | WiFi Network / Router | 1 | For web dashboard access |
| 13 | Blynk / ThingSpeak (optional) | — | Cloud dashboard integration |
🔗 Pin Connections & Wiring
| Voltage Sensor / Pot | D34 | Orange wire |
| Current CH1 (Living Room) | D35 | Green wire |
| Current CH2 (Kitchen) | D32 | Blue wire |
| Current CH3 (Bedroom) | D33 | Yellow wire |
| Relay CH1 (Green LED) | D26 | Living Room |
| Relay CH2 (Blue LED) | D27 | Kitchen |
| Relay CH3 (Yellow LED) | D14 | Bedroom |
| Power LED (White) | D13 | Via 220Ω resistor → GND |
| Alert LED (Red) | D12 | Via 220Ω resistor → GND |
| Buzzer (+) | D25 | Buzzer (–) → GND |
| VCC | 3V3 | Red wire |
| GND | GND | Black wire |
| SDA | D21 | Blue wire |
| SCL | D22 | Yellow wire |
ADC Pins Note
ESP32 pins D34 and D35 are input-only ADC pins — perfect for sensor reading. Avoid using them as output. Also, ADC2 pins (D0, D2, D4) cannot be used when WiFi is active.
📦 Libraries to Install
Install these libraries in the Arduino IDE via Sketch → Include Library → Manage Libraries:
- Adafruit_SSD1306OLED display driverv2.5.x
- Adafruit_GFXGraphics library (dependency)v1.11.x
- WiFi.hBuilt-in ESP32 WiFi libraryBuilt-in
- WebServer.hBuilt-in ESP32 HTTP serverBuilt-in
- Wire.hI2C communication (built-in)Built-in
Board Setup
In Arduino IDE: File → Preferences → Additional Board URLs → add https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json. Then install esp32 by Espressif from Board Manager.
📡 Multi-Channel Monitoring
The system monitors three independent circuits simultaneously, each mapped to a room in the house. Every channel independently tracks its own voltage, current, power, energy, and cost.
🚨 Smart Alert System
The system continuously monitors three alert conditions. When triggered, the Red LED flashes, the buzzer sounds, the OLED shows a warning, and the web dashboard updates.
When Alert Fires:
Red Alert LED turns ON
GPIO 12 drives the red LED HIGH via 220Ω resistor.
Buzzer Sounds
Tone at 2000Hz plays for 300ms using GPIO 25.
OLED Displays "! HIGH POWER !"
The display replaces the uptime counter with a flashing warning message.
Web Dashboard Updates
Status auto-refreshes every 5 seconds — alertActive flag set in JSON API.
🖥️ Web Dashboard Features
Access the dashboard from any browser on your WiFi network by navigating to the ESP32's IP address (printed on Serial Monitor at startup).
API Endpoints
| Endpoint | Method | Description |
|---|---|---|
/ | GET | Main dashboard HTML page |
/status | GET | JSON with all sensor data |
/history | GET | JSON array of power history (50 points) |
/reset | GET | Reset energy & cost counters |
/relay1/on or /off | GET | Control Living Room relay |
/relay2/on or /off | GET | Control Kitchen relay |
/relay3/on or /off | GET | Control Bedroom relay |
📟 OLED Display Layout
The SSD1306 128×64 OLED gives local feedback without needing WiFi. It updates every 500ms and cycles through key metrics.
┌─────────────────────────┐ │ ENERGY MONITOR │ ← Row 0 (title) │─────────────────────────│ ← Row 9 (separator line) │ Power: 1250 W │ ← Row 12 (total power) │ Energy: 0.04 kWh │ ← Row 22 (cumulative energy) │ Cost: $0.00 │ ← Row 32 (total cost) │ L:420 K:530 B:300 │ ← Row 42 (channel breakdown) │ Up: 0h 5m │ ← Row 54 (uptime / alert msg) └─────────────────────────┘
🗂️ Diagram.json — Wokwi Circuit File
Copy this diagram.json into your Wokwi project to set up all component connections automatically.
{
"version": 1,
"author": "ESP32 Energy Monitoring System",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-esp32-devkit-v1", "id": "esp", "top": 0, "left": 0, "attrs": {} },
{ "type": "wokwi-potentiometer", "id": "voltage", "top": -67.2, "left": 268.8, "rotate": 180, "attrs": { "label": "Voltage (220V)" } },
{ "type": "wokwi-potentiometer", "id": "current1", "top": 28.8, "left": 268.8, "rotate": 180, "attrs": { "label": "Current CH1" } },
{ "type": "wokwi-potentiometer", "id": "current2", "top": 124.8, "left": 268.8, "rotate": 180, "attrs": { "label": "Current CH2" } },
{ "type": "wokwi-potentiometer", "id": "current3", "top": 220.8, "left": 268.8, "rotate": 180, "attrs": { "label": "Current CH3" } },
{ "type": "wokwi-led", "id": "relay1", "top": -124.8, "left": 441.6, "attrs": { "color": "green", "lightColor": "green", "label": "Living Room" } },
{ "type": "wokwi-resistor", "id": "r1", "top": -67.2, "left": 441.6, "rotate": 90, "attrs": { "value": "220" } },
{ "type": "wokwi-led", "id": "relay2", "top": -124.8, "left": 518.4, "attrs": { "color": "blue", "lightColor": "blue", "label": "Kitchen" } },
{ "type": "wokwi-resistor", "id": "r2", "top": -67.2, "left": 518.4, "rotate": 90, "attrs": { "value": "220" } },
{ "type": "wokwi-led", "id": "relay3", "top": -124.8, "left": 595.2, "attrs": { "color": "yellow", "lightColor": "yellow", "label": "Bedroom" } },
{ "type": "wokwi-resistor", "id": "r3", "top": -67.2, "left": 595.2, "rotate": 90, "attrs": { "value": "220" } },
{ "type": "wokwi-led", "id": "power_led", "top": 38.4, "left": 441.6, "attrs": { "color": "white", "lightColor": "white", "label": "Power" } },
{ "type": "wokwi-resistor", "id": "r4", "top": 96, "left": 441.6, "rotate": 90, "attrs": { "value": "220" } },
{ "type": "wokwi-led", "id": "alert_led", "top": 38.4, "left": 518.4, "attrs": { "color": "red", "lightColor": "red", "label": "Alert" } },
{ "type": "wokwi-resistor", "id": "r5", "top": 96, "left": 518.4, "rotate": 90, "attrs": { "value": "220" } },
{ "type": "wokwi-buzzer", "id": "bz1", "top": 172.8, "left": 489.6, "attrs": { "volume": "0.3" } },
{ "type": "wokwi-ssd1306", "id": "oled1", "top": 326.4, "left": 268.8, "attrs": { "i2cAddress": "0x3C" } }
],
"connections": [
[ "esp:TX0", "$serialMonitor:RX", "", [] ],
[ "esp:RX0", "$serialMonitor:TX", "", [] ],
[ "voltage:GND", "esp:GND.1", "black", [ "v0" ] ],
[ "voltage:VCC", "esp:3V3", "red", [ "v0" ] ],
[ "voltage:SIG", "esp:D34", "orange", [ "v0" ] ],
[ "current1:GND", "esp:GND.1", "black", [ "v0" ] ],
[ "current1:VCC", "esp:3V3", "red", [ "v0" ] ],
[ "current1:SIG", "esp:D35", "green", [ "v0" ] ],
[ "current2:GND", "esp:GND.2", "black", [ "v0" ] ],
[ "current2:VCC", "esp:3V3", "red", [ "v0" ] ],
[ "current2:SIG", "esp:D32", "blue", [ "v0" ] ],
[ "current3:GND", "esp:GND.2", "black", [ "v0" ] ],
[ "current3:VCC", "esp:3V3", "red", [ "v0" ] ],
[ "current3:SIG", "esp:D33", "yellow", [ "v0" ] ],
[ "relay1:A", "esp:D26", "green", [ "v0" ] ],
[ "relay1:C", "r1:1", "green", [ "v0" ] ],
[ "r1:2", "esp:GND.1", "black", [ "v0" ] ],
[ "relay2:A", "esp:D27", "blue", [ "v0" ] ],
[ "relay2:C", "r2:1", "blue", [ "v0" ] ],
[ "r2:2", "esp:GND.1", "black", [ "v0" ] ],
[ "relay3:A", "esp:D14", "yellow", [ "v0" ] ],
[ "relay3:C", "r3:1", "yellow", [ "v0" ] ],
[ "r3:2", "esp:GND.1", "black", [ "v0" ] ],
[ "power_led:A", "esp:D13", "white", [ "v0" ] ],
[ "power_led:C", "r4:1", "white", [ "v0" ] ],
[ "r4:2", "esp:GND.2", "black", [ "v0" ] ],
[ "alert_led:A", "esp:D12", "red", [ "v0" ] ],
[ "alert_led:C", "r5:1", "red", [ "v0" ] ],
[ "r5:2", "esp:GND.2", "black", [ "v0" ] ],
[ "bz1:1", "esp:D25", "purple", [ "v0" ] ],
[ "bz1:2", "esp:GND.2", "black", [ "v0" ] ],
[ "oled1:VCC", "esp:3V3", "red", [ "h0" ] ],
[ "oled1:GND", "esp:GND.2", "black", [ "h0" ] ],
[ "oled1:SDA", "esp:D21", "blue", [ "h0" ] ],
[ "oled1:SCL", "esp:D22", "yellow", [ "h0" ] ]
],
"dependencies": {}
}
💻 Full Arduino Code
Upload this sketch to your ESP32 using Arduino IDE. Change ssid and password to match your WiFi network.
For Wokwi Simulation
Keep SSID as "Wokwi-GUEST" and password as "" — the simulator provides a free virtual WiFi network.
/* * IoT Energy Monitoring System * Platform: ESP32 DevKit V1 * Author: MakeMindz | www.makemindz.com * * Features: * - Real-time 3-channel voltage & current monitoring * - Power (W), Energy (kWh), Cost ($) calculation * - OLED local display + Web dashboard * - Overload alerts (buzzer + LED) * - Remote relay control via HTTP */ #include <WiFi.h> #include <WebServer.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> // ── WiFi Credentials ────────────────────────── const char* ssid = "Wokwi-GUEST"; const char* password = ""; WebServer server(80); // ── Pin Definitions ─────────────────────────── #define VOLTAGE_SENSOR_PIN 34 #define CURRENT_CH1_PIN 35 #define CURRENT_CH2_PIN 32 #define CURRENT_CH3_PIN 33 #define RELAY_CH1_PIN 26 #define RELAY_CH2_PIN 27 #define RELAY_CH3_PIN 14 #define BUZZER_PIN 25 #define LED_POWER_PIN 13 #define LED_ALERT_PIN 12 // ── OLED Display ────────────────────────────── #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // ── Configuration ───────────────────────────── #define VOLTAGE_CALIBRATION 1.0 #define CURRENT_CALIBRATION 1.0 #define POWER_FACTOR 0.95 #define COST_PER_KWH 0.12 // ── Alert Thresholds ────────────────────────── #define MAX_POWER_WATTS 3000 #define HIGH_POWER_WARNING 2000 #define MAX_CURRENT_AMPS 15 // ── Data Structures ─────────────────────────── struct EnergyChannel { String name; float voltage, current, power, energy, cost; bool relayState; unsigned long lastUpdate; float peakPower; int alertCount; }; struct SystemState { float totalPower, totalEnergy, totalCost, averagePower; unsigned long startTime, lastReset; int displayMode; bool alertActive; float costPerKWh; }; EnergyChannel channels[3]; SystemState energySystem; #define MAX_HISTORY 50 float powerHistory[MAX_HISTORY]; int historyIndex = 0; unsigned long lastSensorRead = 0; unsigned long lastDisplayUpdate = 0; unsigned long lastCostUpdate = 0; const long sensorInterval = 1000; const long displayInterval = 500; const long costUpdateInterval = 60000; // ── Setup ───────────────────────────────────── void setup() { Serial.begin(115200); pinMode(RELAY_CH1_PIN, OUTPUT); pinMode(RELAY_CH2_PIN, OUTPUT); pinMode(RELAY_CH3_PIN, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); pinMode(LED_POWER_PIN, OUTPUT); pinMode(LED_ALERT_PIN, OUTPUT); pinMode(VOLTAGE_SENSOR_PIN, INPUT); pinMode(CURRENT_CH1_PIN, INPUT); pinMode(CURRENT_CH2_PIN, INPUT); pinMode(CURRENT_CH3_PIN, INPUT); channels[0].name = "Living Room"; channels[1].name = "Kitchen"; channels[2].name = "Bedroom"; for (int i = 0; i < 3; i++) { channels[i].voltage = 0; channels[i].current = 0; channels[i].power = 0; channels[i].energy = 0; channels[i].cost = 0; channels[i].relayState = true; channels[i].lastUpdate = millis(); channels[i].peakPower = 0; channels[i].alertCount = 0; digitalWrite(RELAY_CH1_PIN + i, HIGH); } energySystem.totalPower = 0; energySystem.totalEnergy = 0; energySystem.totalCost = 0; energySystem.averagePower = 0; energySystem.startTime = millis(); energySystem.lastReset = millis(); energySystem.displayMode = 0; energySystem.alertActive = false; energySystem.costPerKWh = COST_PER_KWH; // Init OLED if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("OLED failed")); } else { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println("ENERGY MONITOR"); display.println("Initializing..."); display.display(); delay(2000); } // Connect WiFi WiFi.begin(ssid, password); Serial.print("Connecting to WiFi"); display.clearDisplay(); display.setCursor(0, 0); display.println("Connecting WiFi..."); display.display(); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\n✓ WiFi connected!"); Serial.print("Dashboard: http://"); Serial.println(WiFi.localIP()); // Register routes server.on("/", handleRoot); server.on("/status", handleStatus); server.on("/history", handleHistory); server.on("/reset", handleReset); server.on("/relay1/on", []() { controlRelay(0, true); }); server.on("/relay1/off", []() { controlRelay(0, false); }); server.on("/relay2/on", []() { controlRelay(1, true); }); server.on("/relay2/off", []() { controlRelay(1, false); }); server.on("/relay3/on", []() { controlRelay(2, true); }); server.on("/relay3/off", []() { controlRelay(2, false); }); server.begin(); digitalWrite(LED_POWER_PIN, HIGH); tone(BUZZER_PIN, 1000, 100); delay(150); tone(BUZZER_PIN, 1500, 100); updateDisplay(); } // ── Loop ────────────────────────────────────── void loop() { server.handleClient(); unsigned long now = millis(); if (now - lastSensorRead >= sensorInterval) { lastSensorRead = now; readSensors(); calculatePower(); checkAlerts(); } if (now - lastDisplayUpdate >= displayInterval) { lastDisplayUpdate = now; updateDisplay(); } if (now - lastCostUpdate >= costUpdateInterval) { lastCostUpdate = now; updateEnergyCost(); } } // ── Sensor Reading ──────────────────────────── void readSensors() { int voltageRaw = analogRead(VOLTAGE_SENSOR_PIN); float voltage = map(voltageRaw, 0, 4095, 0, 250) * VOLTAGE_CALIBRATION; int r1 = analogRead(CURRENT_CH1_PIN); int r2 = analogRead(CURRENT_CH2_PIN); int r3 = analogRead(CURRENT_CH3_PIN); channels[0].current = map(r1, 0, 4095, 0, 20) * CURRENT_CALIBRATION; channels[1].current = map(r2, 0, 4095, 0, 20) * CURRENT_CALIBRATION; channels[2].current = map(r3, 0, 4095, 0, 20) * CURRENT_CALIBRATION; for (int i = 0; i < 3; i++) { channels[i].voltage = voltage; if (!channels[i].relayState) channels[i].current = 0; } } // ── Power Calculation ───────────────────────── void calculatePower() { energySystem.totalPower = 0; for (int i = 0; i < 3; i++) { channels[i].power = channels[i].voltage * channels[i].current * POWER_FACTOR; if (channels[i].power > channels[i].peakPower) channels[i].peakPower = channels[i].power; unsigned long dt = millis() - channels[i].lastUpdate; float hours = dt / 3600000.0; channels[i].energy += (channels[i].power / 1000.0) * hours; channels[i].lastUpdate = millis(); channels[i].cost = channels[i].energy * energySystem.costPerKWh; energySystem.totalPower += channels[i].power; } energySystem.totalEnergy = channels[0].energy + channels[1].energy + channels[2].energy; energySystem.totalCost = energySystem.totalEnergy * energySystem.costPerKWh; powerHistory[historyIndex] = energySystem.totalPower; historyIndex = (historyIndex + 1) % MAX_HISTORY; float sum = 0; for (int i = 0; i < MAX_HISTORY; i++) sum += powerHistory[i]; energySystem.averagePower = sum / MAX_HISTORY; } // ── Alert Checker ───────────────────────────── void checkAlerts() { bool triggered = false; if (energySystem.totalPower > MAX_POWER_WATTS) triggered = true; if (energySystem.totalPower > HIGH_POWER_WARNING) triggered = true; for (int i = 0; i < 3; i++) { if (channels[i].current > MAX_CURRENT_AMPS) { channels[i].alertCount++; triggered = true; } } if (triggered && !energySystem.alertActive) { energySystem.alertActive = true; digitalWrite(LED_ALERT_PIN, HIGH); tone(BUZZER_PIN, 2000, 300); } else if (!triggered && energySystem.alertActive) { energySystem.alertActive = false; digitalWrite(LED_ALERT_PIN, LOW); } } // ── OLED Update ─────────────────────────────── void updateDisplay() { display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.println("ENERGY MONITOR"); display.drawLine(0, 9, 128, 9, SSD1306_WHITE); display.setCursor(0, 12); display.print("Power: "); display.print(energySystem.totalPower, 0); display.println(" W"); display.setCursor(0, 22); display.print("Energy: "); display.print(energySystem.totalEnergy, 2); display.println(" kWh"); display.setCursor(0, 32); display.print("Cost: $"); display.println(energySystem.totalCost, 2); display.setCursor(0, 42); display.print("L:"); display.print(channels[0].power, 0); display.print(" K:"); display.print(channels[1].power, 0); display.print(" B:"); display.print(channels[2].power, 0); display.setCursor(0, 54); if (energySystem.alertActive) { display.print("! HIGH POWER !"); } else { unsigned long up = (millis() - energySystem.startTime) / 1000; display.print("Up:"); display.print(up/3600); display.print("h "); display.print((up%3600)/60); display.print("m"); } display.display(); } // ── Relay Control ───────────────────────────── void controlRelay(int ch, bool state) { if (ch >= 0 && ch < 3) { channels[ch].relayState = state; digitalWrite(RELAY_CH1_PIN + ch, state ? HIGH : LOW); tone(BUZZER_PIN, state ? 1200 : 800, 100); server.sendHeader("Location", "/"); server.send(303); } } void updateEnergyCost() { for (int i = 0; i < 3; i++) channels[i].cost = channels[i].energy * energySystem.costPerKWh; energySystem.totalCost = energySystem.totalEnergy * energySystem.costPerKWh; } // ── Web Handlers (see full source for HTML) ─── void handleRoot() { /* Full dark-theme dashboard HTML */ } void handleStatus() { /* Returns JSON with all sensor data */ } void handleHistory() { /* Returns JSON array of power history */ } void handleReset() { for (int i = 0; i < 3; i++) { channels[i].energy = channels[i].cost = channels[i].peakPower = 0; channels[i].alertCount = 0; } energySystem.totalEnergy = energySystem.totalCost = 0; energySystem.lastReset = millis(); server.sendHeader("Location", "/"); server.send(303); }
Comments
Post a Comment