Top 5 ESP32 Smart Energy Monitor Projects (With Code & Circuit)
With electricity bills at record highs, energy monitoring is one of the most practically useful things you can build. These 5 projects let you measure exactly how much power your appliances, solar panels, and batteries are using — in real time, on your phone, or logged to the cloud. All are non-invasive (no mains wiring needed) or use safe low-voltage DC measurements.
Project 1 — Smart Plug Power Meter
Project Description
A PZEM-004T module measures the voltage, current, power, frequency, and energy (kWh) of any mains appliance plugged into it — all in one chip. The ESP32 reads the data over UART and displays it on a web page. Great for finding out which appliances are silently costing you money.
Components: ESP32, PZEM-004T v3.0 module (includes CT clamp), IEC socket and plug for enclosure, breadboard, jumper wires. Note: the PZEM-004T connects to mains — have a qualified electrician build the enclosure if you are unfamiliar with mains wiring.
Circuit Description
| PZEM-004T | ESP32 Pin |
|---|---|
| TX | GPIO 16 (UART2 RX) |
| RX | GPIO 17 (UART2 TX) |
| 5V | 5V (Vin) |
| GND | GND |
Code
#include <WiFi.h>
#include <WebServer.h>
#include <PZEM004Tv30.h>
PZEM004Tv30 pzem(Serial2, 16, 17);
WebServer server(80);
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
void handleRoot() {
float voltage = pzem.voltage();
float current = pzem.current();
float power = pzem.power();
float energy = pzem.energy();
float freq = pzem.frequency();
float pf = pzem.pf();
String html = "";
html += "";
html += "Power Meter ";
html += "";
html += "⚡ Smart Power Meter
";
html += "Parameter Value ";
html += "Voltage " + String(voltage,1) + " V ";
html += "Current " + String(current,3) + " A ";
html += "Power " + String(power,1) + " W ";
html += "Energy " + String(energy,3) + " kWh ";
html += "Frequency "+ String(freq,1) + " Hz ";
html += "Power Factor " + String(pf,2) + " ";
html += "
";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println("\nIP: " + WiFi.localIP().toString());
server.on("/", handleRoot);
server.begin();
}
void loop() { server.handleClient(); }
Conclusion
The PZEM-004T gives you professional-grade power measurement in a module that costs under £5. Next step: log kWh readings to ThingSpeak every hour and calculate your monthly electricity cost automatically.
Project 2 — Solar Panel Output Monitor
Project Description
Monitor a 12V solar panel's voltage and current output using a voltage divider and an INA219 current sensor. The ESP32 calculates power output in watts, logs daily energy generation, and pushes everything to a ThingSpeak dashboard. See exactly when your panel peaks and how much energy it generated each day.
Components: ESP32, INA219 current/power sensor module, voltage divider (2 x resistors), 12V solar panel, breadboard, jumper wires.
Circuit Description
| INA219 | ESP32 Pin |
|---|---|
| SDA | GPIO 21 |
| SCL | GPIO 22 |
| VIN+ | Solar panel + terminal |
| VIN– | Load + terminal (shunt in series) |
| VCC | 3.3V |
| GND | GND |
The INA219 measures up to 26V and 3.2A. For higher current panels, use the INA226 instead.
Code
#include <WiFi.h>
#include <HTTPClient.h>
#include <Adafruit_INA219.h>
Adafruit_INA219 ina219;
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* tsApiKey = "YOUR_THINGSPEAK_WRITE_KEY";
float dailyEnergyWh = 0;
unsigned long lastLog = 0;
void setup() {
Serial.begin(115200);
ina219.begin();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("Solar monitor online.");
}
void loop() {
float voltage = ina219.getBusVoltage_V();
float current = ina219.getCurrent_mA() / 1000.0; // convert to A
float power = voltage * current;
// Accumulate energy (Wh) — readings every 60s
dailyEnergyWh += power / 60.0; // Wh = W * (1/60) hour
Serial.printf("V:%.2f I:%.3f A P:%.2f W Daily:%.2f Wh\n",
voltage, current, power, dailyEnergyWh);
// Push to ThingSpeak every 60 seconds
if (millis() - lastLog >= 60000) {
lastLog = millis();
HTTPClient http;
String url = "https://api.thingspeak.com/update?api_key=" + String(tsApiKey) +
"&field1=" + String(voltage,2) +
"&field2=" + String(current,3) +
"&field3=" + String(power,2) +
"&field4=" + String(dailyEnergyWh,2);
http.begin(url); http.GET(); http.end();
}
delay(60000);
}
Conclusion
Monitoring solar output with real data helps you optimise panel placement and understand seasonal variation. Next step: add a DS3231 RTC to reset the daily energy counter at midnight, and store 30-day history on an SD card.
Project 3 — LiPo / Lead-Acid Battery Health Monitor
Project Description
Monitor a battery's voltage, state of charge (%), and charge/discharge current. A voltage divider scales the battery voltage to the ESP32's 3.3V ADC range; the INA219 measures current direction (charging vs discharging). An OLED shows all readings in real time and a Telegram alert fires when charge drops below 20%.
Components: ESP32, INA219, 0.96" OLED, 2 x resistors for voltage divider (100kΩ + 33kΩ for 12V battery), breadboard, jumper wires.
Circuit Description
| Component | ESP32 Pin |
|---|---|
| INA219 SDA/SCL | GPIO 21/22 |
| OLED SDA/SCL | GPIO 21/22 (shared bus) |
| Voltage divider midpoint | GPIO 34 (ADC) |
Voltage divider: 100kΩ from battery+ to GPIO34, 33kΩ from GPIO34 to GND. This scales 0–16V down to 0–3.3V for the ESP32 ADC. Calibrate by measuring actual battery voltage with a multimeter.
Code
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <HTTPClient.h>
Adafruit_INA219 ina219;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID = "YOUR_CHAT_ID";
#define VOLT_PIN 34
#define VOLT_FACTOR 4.818 // calibrate: (R1+R2)/R2 = (100k+33k)/33k
#define BATT_MAX 12.6 // fully charged LiPo 3S
#define BATT_MIN 10.5 // cutoff voltage
bool lowAlertSent = false;
float batteryPercent(float v) {
return constrain(map((int)(v*100), (int)(BATT_MIN*100), (int)(BATT_MAX*100), 0, 100), 0, 100);
}
void setup() {
Serial.begin(115200);
ina219.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextColor(WHITE);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
}
void loop() {
int raw = analogRead(VOLT_PIN);
float vBatt = (raw / 4095.0) * 3.3 * VOLT_FACTOR;
float curr = ina219.getCurrent_mA();
float pct = batteryPercent(vBatt);
String state = (curr > 10) ? "CHG" : (curr < -10) ? "DIS" : "IDLE";
display.clearDisplay();
display.setTextSize(1); display.setCursor(0,0); display.println("Battery Monitor");
display.setTextSize(2); display.setCursor(0,16);
display.printf("%.1fV", vBatt);
display.setTextSize(1); display.setCursor(0,40);
display.printf("%.0f%% | %.0fmA | %s", pct, curr, state.c_str());
display.display();
Serial.printf("%.2fV | %.0f%% | %.0fmA | %s\n", vBatt, pct, curr, state.c_str());
if (pct < 20 && !lowAlertSent) {
HTTPClient http;
String url = "https://api.telegram.org/bot" + String(botToken) +
"/sendMessage?chat_id=" + chatID +
"&text=🔋 Battery LOW: " + String(pct,0) + "% (" + String(vBatt,2) + "V)";
http.begin(url); http.GET(); http.end();
lowAlertSent = true;
}
if (pct >= 25) lowAlertSent = false;
delay(5000);
}
Conclusion
Battery monitoring is critical for any off-grid system. Next step: add a relay that disconnects the load at the cutoff voltage to protect the battery from deep discharge, and another relay to disconnect charging at 100% to prevent overcharge.
Project 4 — EV Charger Session Display
Project Description
Monitor your home EV charger's energy usage using a PZEM-004T on the charger's supply line. The ESP32 tracks session start time, kWh consumed, cost, and estimated range added — displayed on a 2.4" TFT screen in your garage. Finally know exactly how much each charge costs.
Components: ESP32, PZEM-004T v3.0, 2.4" ILI9341 TFT display, reed switch on charger cable (to detect plug-in), breadboard, jumper wires.
Circuit Description
| Component | ESP32 Pin |
|---|---|
| PZEM TX/RX | GPIO 16/17 (UART2) |
| TFT MOSI/SCK/CS/DC/RST | GPIO 23/18/15/2/4 |
| Reed switch (charger connected) | GPIO 5 (INPUT_PULLUP) |
Code
#include <PZEM004Tv30.h>
#include <TFT_eSPI.h>
PZEM004Tv30 pzem(Serial2, 16, 17);
TFT_eSPI tft = TFT_eSPI();
#define REED_PIN 5
#define COST_PER_KWH 0.28 // £/kWh — adjust to your tariff
#define KM_PER_KWH 6.0 // typical EV efficiency (adjust for your car)
float sessionStartEnergy = 0;
unsigned long sessionStart = 0;
bool charging = false;
void setup() {
Serial.begin(115200);
tft.init(); tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
pinMode(REED_PIN, INPUT_PULLUP);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(2);
tft.setCursor(20, 100); tft.println("Waiting for EV...");
}
void loop() {
bool plugged = (digitalRead(REED_PIN) == LOW);
if (plugged && !charging) {
charging = true;
sessionStartEnergy = pzem.energy();
sessionStart = millis();
tft.fillScreen(TFT_DARKGREEN);
Serial.println("Charging session started.");
} else if (!plugged && charging) {
charging = false;
tft.fillScreen(TFT_BLACK);
tft.setCursor(10, 100); tft.setTextSize(2);
tft.println("Session complete!");
}
if (charging) {
float kwhUsed = pzem.energy() - sessionStartEnergy;
float power = pzem.power();
float cost = kwhUsed * COST_PER_KWH;
float km = kwhUsed * KM_PER_KWH;
unsigned long elapsed = (millis() - sessionStart) / 60000;
tft.fillScreen(TFT_NAVY);
tft.setTextColor(TFT_WHITE, TFT_NAVY);
tft.setTextSize(1); tft.setCursor(10, 5); tft.println("EV Charge Session");
tft.setTextSize(2);
tft.setCursor(10, 25); tft.printf("%.2f kWh", kwhUsed);
tft.setCursor(10, 55); tft.printf("%.2f W", power);
tft.setCursor(10, 85); tft.printf("%.2f GBP", cost);
tft.setCursor(10,115); tft.printf("+%.0f km", km);
tft.setTextSize(1);
tft.setCursor(10,145); tft.printf("Time: %lu min", elapsed);
}
delay(2000);
}
Conclusion
Knowing the exact cost of each EV charge session makes a real difference to household budgeting — especially with time-of-use tariffs. Next step: connect to WiFi and log every session to a spreadsheet automatically, or add a schedule relay to only charge during cheap overnight tariff hours.
Project 5 — Whole-Home Power Dashboard
Project Description
Three SCT-013 non-invasive current transformers clipped onto the main consumer unit feed, the solar inverter output, and the EV charger circuit give you a complete picture: total consumption, solar generation, and net grid import/export — all on a live web dashboard. This is the DIY version of commercial whole-home energy monitors costing hundreds of pounds.
Components: ESP32, 3 x SCT-013-030 (30A) current transformers, 3 x 10µF capacitors, 6 x 10kΩ resistors, breadboard, jumper wires.
Circuit Description
| Circuit | SCT-013 | ESP32 ADC Pin |
|---|---|---|
| Main feed | SCT #1 | GPIO 34 |
| Solar inverter | SCT #2 | GPIO 35 |
| EV charger | SCT #3 | GPIO 32 |
Each SCT-013 needs its own 2.5V bias circuit (two 10kΩ resistors from 3.3V to GND, midpoint to ADC pin) and a 10µF bypass capacitor. The SCT-013-030 has a built-in 33Ω burden resistor.
Code
#include <WiFi.h>
#include <WebServer.h>
#include "EmonLib.h"
EnergyMonitor emon1, emon2, emon3;
WebServer server(80);
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
#define VOLTAGE 230.0
#define CALIBRATION 111.1
void handleRoot() {
double i1 = emon1.calcIrms(1480); double p1 = i1 * VOLTAGE;
double i2 = emon2.calcIrms(1480); double p2 = i2 * VOLTAGE;
double i3 = emon3.calcIrms(1480); double p3 = i3 * VOLTAGE;
double net = p1 - p2; // positive = importing, negative = exporting
String html = "";
html += "Home Energy ";
html += "";
html += "🏠 Home Energy Dashboard
";
html += "" + String(p1,0) + "WTotal Consumption";
html += "" + String(p2,0) + "WSolar Generation";
html += "" + String(p3,0) + "WEV Charger";
String netLabel = (net > 0) ? "Importing" : "Exporting";
html += "" + String(abs(net),0) + "WGrid " + netLabel + "";
html += "";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
emon1.current(34, CALIBRATION);
emon2.current(35, CALIBRATION);
emon3.current(32, CALIBRATION);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println("\nDashboard: http://" + WiFi.localIP().toString());
server.on("/", handleRoot);
server.begin();
}
void loop() { server.handleClient(); }
Conclusion
A whole-home energy dashboard gives you the data to make real decisions — running the dishwasher when solar is peaking, charging the EV overnight on a cheap tariff, or identifying the appliance costing you £50/month. Next step: push readings to Home Assistant over MQTT and set automations that turn on heavy loads when solar generation is high.
0 Comments