Building a Smart Energy Monitor with ESP32
If you have ever opened your monthly electricity bill and wondered where all that power went, you are not alone. Most Indian households pay anywhere from Rs 1,500 to Rs 5,000 per month on electricity, and the bill rarely tells you which appliance is the culprit. Is it the old refrigerator running 24/7? The water heater you forgot to turn off? The AC unit that cycles more than you think?
A smart energy monitor solves this mystery. In this project, we will build one from scratch using an ESP32, a non-invasive current sensor (SCT-013-030), and an OLED display. By the end, you will have a device that shows live power consumption in watts, tracks daily kilowatt-hours, estimates your monthly bill in rupees, and serves a web dashboard you can check from your phone.
Total cost: roughly Rs 800 — compared to Rs 3,000 or more for a commercial smart energy monitor.
Why Monitor Energy at Home?
Indian electricity tariffs follow a slab-based pricing model. The more you consume, the higher the per-unit rate. For example, BESCOM (Bangalore) charges around Rs 4.15 per unit for the first 50 units, but Rs 7.80 per unit once you cross 200 units. This means a small reduction in consumption can produce a disproportionately large drop in your bill by keeping you in a lower slab.
Without measurement, you are guessing. With a real-time energy monitor, you can:
- Identify power-hungry appliances by clamping the sensor on individual circuits
- Track daily consumption patterns and find wasteful habits
- Get alerted when consumption exceeds a threshold
- Verify your electricity bill against your own readings
- Optimize usage to stay within a lower tariff slab
How AC Current Sensing Works
There are two common approaches for measuring alternating current in a household wire.
Current Transformer (CT) — Non-Invasive
A current transformer like the SCT-013-030 is a split-core transformer that clamps around a wire. The wire carrying current acts as the primary winding (a single turn), and the sensor's internal coil acts as the secondary winding (thousands of turns). By electromagnetic induction, the sensor outputs a small current proportional to the current flowing through the wire.
Key advantage: You never cut or strip any wire. You simply clamp it around the insulated cable. This is the method we will use.
Hall Effect Sensor — Invasive
The ACS712 is a Hall effect-based sensor. It requires the current-carrying wire to pass through the sensor module — meaning you must break the circuit and solder or screw the wire into the module. It is accurate and works for both AC and DC, but the invasive installation makes it unsuitable for mains-level monitoring in most home setups.
| Feature | SCT-013-030 (CT) | ACS712 (Hall Effect) |
|---|---|---|
| Installation | Clamp-on, non-invasive | Inline, invasive |
| Max Current | 30A | 5A / 20A / 30A variants |
| Output | AC current (needs burden resistor) | Analog voltage |
| Safety | Excellent (isolated) | Requires careful wiring |
| Best For | Mains monitoring | DC circuits, low-voltage projects |
For home energy monitoring, the SCT-013-030 is the clear winner.
Understanding the SCT-013-030
The SCT-013-030 is rated for 0-30A AC and outputs a proportional current (not voltage). Some variants like the SCT-013-000 output a raw current that requires an external burden resistor, while the SCT-013-030 has a built-in burden resistor that outputs 0-1V.
Important clarification: The SCT-013-030 (with the "030" suffix) has a built-in burden resistor and outputs 0-1V AC for 0-30A input. If you have the SCT-013-000 variant, you need to add your own 33 ohm burden resistor across the output terminals.
In this guide, we will use the SCT-013-000 (no built-in burden resistor) because it gives us more flexibility and is more commonly available in India. We will add the burden resistor ourselves.
Important Limitation: Current vs Power
The SCT-013 measures current only. To calculate power (watts), you need both current and voltage:
Power (W) = Voltage (V) x Current (A) x Power Factor
For a simple home monitor, we assume:
- Voltage: 230V (Indian standard mains voltage)
- Power Factor: 1.0 for resistive loads (heaters, incandescent bulbs), approximately 0.85 for inductive loads (motors, ACs, refrigerators)
For this project, we will assume a power factor of 0.95 as a reasonable average for a mixed household load. For higher accuracy, you would add a voltage sensing circuit using a small transformer, but that adds significant complexity.
Hardware Required
| Component | Purpose | Approx. Price (Rs) |
|---|---|---|
| ESP32 DevKit V1 | Microcontroller with WiFi | 450 |
| SCT-013-000 (30A) | Non-invasive current sensor | 180 |
| 33 ohm burden resistor (1/4W) | Convert CT current to voltage | 5 |
| 2x 100K ohm resistors | Voltage divider for DC bias | 5 |
| 10uF electrolytic capacitor | Smoothing for bias voltage | 5 |
| 0.96" I2C OLED Display (SSD1306) | Local display | 150 |
| 3.5mm audio jack (female) | Connector for SCT-013 cable | 15 |
| Breadboard + jumper wires | Prototyping | 60 |
| Total | ~Rs 870 |
All components are available at wavtron.in.
The Biasing Circuit: Why You Need a DC Offset
This is the part most tutorials gloss over, but it is critical to understand.
The SCT-013 outputs an AC signal — it swings positive and negative, centered around 0V. The ESP32's ADC can only read 0V to 3.3V. If you connect the sensor directly, the negative half of the waveform gets clipped, and your readings will be completely wrong.
The solution is a DC biasing circuit that shifts the AC signal up so it oscillates around 1.65V (half of 3.3V) instead of 0V.
How the Bias Circuit Works
- Voltage divider: Two 100K ohm resistors connected between 3.3V and GND create a mid-point at 1.65V.
- Smoothing capacitor: A 10uF capacitor across the mid-point to GND stabilizes the 1.65V reference.
- Burden resistor: A 33 ohm resistor across the CT output terminals converts the CT's current output to a voltage.
- Coupling: One CT terminal connects to the bias mid-point. The AC signal now oscillates around 1.65V.
The ESP32 ADC sees a signal that ranges from roughly 0.5V to 2.8V (depending on load), well within its 0-3.3V readable range.
Wiring Diagram
Here is the complete wiring description. Connect the components as follows:
CT Sensor Circuit
3.3V ----[100K]----+----[100K]---- GND
|
[10uF] (to GND)
|
+---- CT Terminal 1
|
[33 ohm burden]
|
+---- CT Terminal 2 ---- GPIO 36 (VP / ADC1_CH0)
OLED Display (I2C)
| OLED Pin | ESP32 Pin |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SDA | GPIO 21 |
| SCL | GPIO 22 |
Summary of ESP32 Connections
| ESP32 Pin | Connected To |
|---|---|
| GPIO 36 (VP) | CT sensor output (via burden + bias circuit) |
| GPIO 21 (SDA) | OLED SDA |
| GPIO 22 (SCL) | OLED SCL |
| 3.3V | OLED VCC, bias voltage divider top |
| GND | OLED GND, bias voltage divider bottom, capacitor |
Software Setup
Install these libraries in Arduino IDE (or PlatformIO):
- EmonLib by OpenEnergyMonitor — for accurate RMS current measurement
- Adafruit SSD1306 — for OLED display
- Adafruit GFX — dependency for the display library
- WiFi (built into ESP32 core)
- WebServer (built into ESP32 core)
- SPIFFS (built into ESP32 core)
The Code
Part 1: Reading RMS Current
The core of energy monitoring is reading the RMS (Root Mean Square) value of the AC current. RMS is the effective value — it accounts for the sinusoidal nature of AC and gives you a number directly usable for power calculation.
EmonLib handles the complex sampling and math for us:
#include "EmonLib.h"
EnergyMonitor emon1;
void setup() {
Serial.begin(115200);
// Current pin = GPIO 36, Calibration = 30.0
// Calibration factor = (CT ratio / burden resistance)
// For SCT-013-000 with 33 ohm burden: 1800 turns / 33 ohms = 54.5
// But we start with 30.0 and calibrate with a known load later
emon1.current(36, 30.0);
}
void loop() {
double Irms = emon1.calcIrms(1480); // 1480 samples
double power = Irms * 230.0 * 0.95; // Watts (assuming 230V, PF=0.95)
Serial.print("Current: ");
Serial.print(Irms, 2);
Serial.print(" A | Power: ");
Serial.print(power, 1);
Serial.println(" W");
delay(1000);
}
How the calibration factor works: The SCT-013-000 has a turns ratio of 1800:1. With a 33 ohm burden resistor, the theoretical calibration is 1800/33 = 54.5. However, component tolerances mean you should calibrate against a known load. We start with 30.0 and adjust later.
Part 2: Calculating kWh and Estimated Cost
// Add these global variables
unsigned long lastMillis = 0;
double dailyKwh = 0.0;
double monthlyEstimateRs = 0.0;
// Tariff slabs (example: BESCOM Bangalore, domestic)
// Slab 1: 0-50 units -> Rs 4.15/unit
// Slab 2: 51-100 units -> Rs 5.60/unit
// Slab 3: 101-200 units -> Rs 7.15/unit
// Slab 4: 200+ units -> Rs 8.20/unit
double calculateMonthlyCost(double monthlyKwh) {
double cost = 0.0;
double remaining = monthlyKwh;
if (remaining > 200) {
cost += (remaining - 200) * 8.20;
remaining = 200;
}
if (remaining > 100) {
cost += (remaining - 100) * 7.15;
remaining = 100;
}
if (remaining > 50) {
cost += (remaining - 50) * 5.60;
remaining = 50;
}
cost += remaining * 4.15;
return cost;
}
void updateEnergy(double powerWatts) {
unsigned long now = millis();
unsigned long elapsed = now - lastMillis;
lastMillis = now;
// Convert power (W) and time (ms) to kWh
// kWh = W * hours = W * (ms / 3600000)
double hours = elapsed / 3600000.0;
dailyKwh += (powerWatts * hours) / 1000.0;
// Estimate monthly from daily average
double monthlyKwh = dailyKwh * 30.0; // Rough estimate
monthlyEstimateRs = calculateMonthlyCost(monthlyKwh);
}
The slab rates above are approximate BESCOM (Bangalore) rates. Replace these with your state electricity board's tariff. Common boards and their approximate rates:
| State | Board | Approx. Rate (Rs/unit, 100-200 slab) |
|---|---|---|
| Karnataka | BESCOM | 7.15 |
| Tamil Nadu | TANGEDCO | 4.50 |
| Maharashtra | MSEDCL | 9.30 |
| Delhi | BSES/Tata | 6.50 |
| Kerala | KSEB | 5.50 |
| Telangana | TSSPDCL | 7.50 |
Part 3: OLED Display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
void setupDisplay() {
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 allocation failed");
return;
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.display();
}
void updateDisplay(double irms, double power, double kwh, double costRs) {
display.clearDisplay();
// Header
display.setTextSize(1);
display.setCursor(0, 0);
display.print("ENERGY MONITOR");
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
// Live power — large font
display.setTextSize(2);
display.setCursor(0, 16);
display.print(power, 0);
display.setTextSize(1);
display.print(" W");
// Current
display.setTextSize(1);
display.setCursor(0, 36);
display.print("Current: ");
display.print(irms, 2);
display.print(" A");
// Daily kWh
display.setCursor(0, 46);
display.print("Today: ");
display.print(kwh, 3);
display.print(" kWh");
// Monthly estimate
display.setCursor(0, 56);
display.print("Month: Rs ");
display.print(costRs, 0);
display.display();
}
Part 4: Web Dashboard
This is where the ESP32 really shines. We serve a complete web dashboard over WiFi that you can access from any device on your local network.
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
WebServer server(80);
// Global variables accessible to the web handler
double currentIrms = 0.0;
double currentPower = 0.0;
double currentDailyKwh = 0.0;
double currentMonthlyCost = 0.0;
void handleRoot() {
String html = R"rawhtml(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Energy Monitor</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
background: #0F1A2E;
color: #f1f5f9;
min-height: 100vh;
padding: 20px;
}
h1 {
text-align: center;
color: #FF6B35;
margin-bottom: 24px;
font-size: 1.5rem;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
max-width: 500px;
margin: 0 auto;
}
.card {
background: #1a2744;
border-radius: 16px;
padding: 20px;
text-align: center;
border: 1px solid rgba(255,255,255,0.08);
}
.card.wide { grid-column: 1 / -1; }
.value {
font-size: 2.5rem;
font-weight: 700;
color: #FF6B35;
}
.unit { font-size: 1rem; color: #64748B; }
.label {
font-size: 0.8rem;
color: #64748B;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-top: 8px;
}
.cost .value { color: #10B981; }
</style>
</head>
<body>
<h1>Smart Energy Monitor</h1>
<div class="grid">
<div class="card wide">
<div class="value" id="power">--</div>
<div class="unit">Watts</div>
<div class="label">Live Power</div>
</div>
<div class="card">
<div class="value" id="current">--</div>
<div class="unit">Amps</div>
<div class="label">Current</div>
</div>
<div class="card">
<div class="value" id="kwh">--</div>
<div class="unit">kWh</div>
<div class="label">Today</div>
</div>
<div class="card wide cost">
<div class="value" id="cost">--</div>
<div class="unit">Rupees (estimated)</div>
<div class="label">Monthly Bill Estimate</div>
</div>
</div>
<script>
function fetchData() {
fetch('/data')
.then(r => r.json())
.then(d => {
document.getElementById('power').textContent = d.power.toFixed(0);
document.getElementById('current').textContent = d.current.toFixed(2);
document.getElementById('kwh').textContent = d.kwh.toFixed(3);
document.getElementById('cost').textContent = '\u20B9' + d.cost.toFixed(0);
})
.catch(e => console.error('Fetch error:', e));
}
fetchData();
setInterval(fetchData, 2000);
</script>
</body>
</html>
)rawhtml";
server.send(200, "text/html", html);
}
void handleData() {
String json = "{";
json += "\"current\":" + String(currentIrms, 2) + ",";
json += "\"power\":" + String(currentPower, 1) + ",";
json += "\"kwh\":" + String(currentDailyKwh, 3) + ",";
json += "\"cost\":" + String(currentMonthlyCost, 0);
json += "}";
server.send(200, "application/json", json);
}
void setupWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/data", handleData);
server.begin();
}
Part 5: Complete Sketch
Here is the full combined code. Copy this into a single .ino file:
#include "EmonLib.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <WebServer.h>
// ---- Configuration ----
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
#define CT_PIN 36 // GPIO 36 (VP) — ADC1 channel
#define CT_CALIBRATION 30.0 // Adjust during calibration
#define MAINS_VOLTAGE 230.0
#define POWER_FACTOR 0.95
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
// ---- Objects ----
EnergyMonitor emon1;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
WebServer server(80);
// ---- State ----
double currentIrms = 0.0;
double currentPower = 0.0;
double dailyKwh = 0.0;
double monthlyEstimateRs = 0.0;
unsigned long lastSampleMillis = 0;
unsigned long lastDisplayMillis = 0;
// ---- Tariff Calculation (BESCOM example) ----
double calculateMonthlyCost(double monthlyKwh) {
double cost = 0.0;
double r = monthlyKwh;
if (r > 200) { cost += (r - 200) * 8.20; r = 200; }
if (r > 100) { cost += (r - 100) * 7.15; r = 100; }
if (r > 50) { cost += (r - 50) * 5.60; r = 50; }
cost += r * 4.15;
return cost;
}
// ---- Web Handlers ----
// (Include handleRoot and handleData from Part 4 above)
void setup() {
Serial.begin(115200);
// Current sensor
emon1.current(CT_PIN, CT_CALIBRATION);
// OLED
Wire.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED init failed");
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(20, 28);
display.print("Starting...");
display.display();
// WiFi
WiFi.begin(ssid, password);
Serial.print("WiFi connecting");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 40) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println();
Serial.print("IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi failed — running offline");
}
server.on("/", handleRoot);
server.on("/data", handleData);
server.begin();
lastSampleMillis = millis();
}
void loop() {
server.handleClient();
unsigned long now = millis();
// Sample current every second
if (now - lastSampleMillis >= 1000) {
currentIrms = emon1.calcIrms(1480);
if (currentIrms < 0.05) currentIrms = 0; // Noise floor
currentPower = currentIrms * MAINS_VOLTAGE * POWER_FACTOR;
// Accumulate energy
double hours = (now - lastSampleMillis) / 3600000.0;
dailyKwh += (currentPower * hours) / 1000.0;
// Monthly estimate (extrapolate from today)
monthlyEstimateRs = calculateMonthlyCost(dailyKwh * 30.0);
lastSampleMillis = now;
// Serial output
Serial.printf("%.2f A | %.0f W | %.3f kWh | Rs %.0f/mo\n",
currentIrms, currentPower, dailyKwh, monthlyEstimateRs);
}
// Update OLED every 2 seconds
if (now - lastDisplayMillis >= 2000) {
updateDisplay(currentIrms, currentPower, dailyKwh, monthlyEstimateRs);
lastDisplayMillis = now;
}
}
Note: You will need to include the handleRoot(), handleData(), and updateDisplay() functions from Parts 3 and 4 in your final sketch. They are separated above for clarity.
Calibration Process
Calibration is essential for accurate readings. Here is the step-by-step process:
-
Get a known load. A 100W incandescent bulb is ideal because it is purely resistive (power factor = 1.0). LED bulbs have variable power factors and are harder to calibrate against.
-
Clamp the CT sensor around one of the two wires going to the bulb (not both — clamping around both wires gives a zero reading because the currents cancel out).
-
Turn on the bulb and note the power reading on the serial monitor.
-
Calculate the expected current: 100W / 230V = 0.435A
-
Adjust the calibration factor:
- If reading shows 0.30A, your calibration is too low. New factor = old factor x (0.435 / 0.30) = 30.0 x 1.45 = 43.5
- If reading shows 0.60A, your calibration is too high. New factor = old factor x (0.435 / 0.60) = 30.0 x 0.725 = 21.75
-
Update
CT_CALIBRATIONin the code, re-upload, and verify. -
Cross-check with a second known load (e.g., a 1000W iron) to confirm linearity.
Adding Data Logging with SPIFFS
To track consumption over time, we can store hourly readings to the ESP32's built-in flash filesystem (SPIFFS).
#include <SPIFFS.h>
void setupSPIFFS() {
if (!SPIFFS.begin(true)) {
Serial.println("SPIFFS mount failed");
return;
}
}
void logHourlyReading(double kwh) {
File file = SPIFFS.open("/energy_log.csv", FILE_APPEND);
if (!file) {
Serial.println("Failed to open log file");
return;
}
// Format: timestamp_ms, cumulative_kwh
String line = String(millis()) + "," + String(kwh, 4) + "\n";
file.print(line);
file.close();
Serial.println("Logged: " + line);
}
// Add a web endpoint to download the log
void handleDownloadLog() {
File file = SPIFFS.open("/energy_log.csv", FILE_READ);
if (!file) {
server.send(404, "text/plain", "No log file found");
return;
}
server.streamFile(file, "text/csv");
file.close();
}
// In setup(), add:
// server.on("/log", handleDownloadLog);
Call logHourlyReading(dailyKwh) every hour using a millis-based timer. The CSV can be downloaded by visiting http://<esp32-ip>/log and opened in Excel or Google Sheets for analysis.
Note: SPIFFS has limited space (typically 1.5MB on ESP32). Each log line is about 20 bytes, so you can store roughly 75,000 readings before running out of space — enough for about 8 years of hourly logging. Still, consider adding a /clear-log endpoint for maintenance.
Safety Considerations
Working near mains electricity requires caution. Follow these rules without exception:
- Never open the CT clamp while it is on a live wire under heavy load. Opening the clamp while current flows creates a dangerous high-voltage spike across the secondary. Turn off the circuit breaker first, or at minimum ensure the load is minimal.
- Never clamp the CT around both wires of a cable simultaneously. The magnetic fields cancel and you read zero. Separate the live and neutral wires, or use the CT at the distribution board where individual wires are accessible.
- The SCT-013 is rated for 600V insulation. Do not use it on wires carrying more than 600V.
- Keep the ESP32 and breadboard away from mains terminals. Use a proper enclosure if this is a permanent installation.
- Use a proper 3.5mm audio jack to connect the CT sensor cable. Do not strip and solder the CT cable directly — you may damage the internal wiring.
- If you are not comfortable working near your distribution board, get a licensed electrician to help with clamping the sensor. The rest of the project is low-voltage and safe.
Extending the Project
Once the basic monitor is working, there are several ways to make it more powerful.
Multiple Channels
The ESP32 has multiple ADC1 pins (GPIO 32, 33, 34, 35, 36, 39). You can connect up to 6 CT sensors to monitor individual circuits — lights, AC, kitchen, geyser, etc. Create an array of EnergyMonitor objects and cycle through them.
MQTT Integration with Home Assistant
For long-term data storage and beautiful dashboards, send data to Home Assistant via MQTT:
#include <PubSubClient.h>
WiFiClient espClient;
PubSubClient mqtt(espClient);
void publishToMQTT(double power, double kwh) {
if (!mqtt.connected()) {
mqtt.connect("energy-monitor");
}
mqtt.publish("home/energy/power", String(power, 1).c_str());
mqtt.publish("home/energy/kwh_today", String(kwh, 3).c_str());
}
In Home Assistant, these appear as entities that you can plot on the Energy Dashboard, set automations (e.g., notify if power exceeds 3000W), and track historical trends.
Blynk App Integration
For a quick mobile dashboard without running a server, Blynk is excellent:
#define BLYNK_TEMPLATE_ID "TMPLxxxxxxxx"
#define BLYNK_TEMPLATE_NAME "Energy Monitor"
#define BLYNK_AUTH_TOKEN "your-auth-token"
#include <BlynkSimpleEsp32.h>
BlynkTimer timer;
void sendToBlynk() {
Blynk.virtualWrite(V0, currentPower); // Watts gauge
Blynk.virtualWrite(V1, currentIrms); // Amps
Blynk.virtualWrite(V2, dailyKwh); // kWh today
Blynk.virtualWrite(V3, monthlyEstimateRs); // Cost estimate
}
// In setup():
// Blynk.begin(BLYNK_AUTH_TOKEN, ssid, password);
// timer.setInterval(2000L, sendToBlynk);
// In loop():
// Blynk.run();
// timer.run();
Over-the-Air (OTA) Updates
Once the monitor is installed inside an enclosure near your distribution board, you do not want to pull it out to upload new code. Add OTA support:
#include <ArduinoOTA.h>
// In setup():
ArduinoOTA.setHostname("energy-monitor");
ArduinoOTA.begin();
// In loop():
ArduinoOTA.handle();
Now you can upload firmware wirelessly from Arduino IDE.
Cost Comparison
| Item | DIY (This Project) | Commercial Smart Monitor |
|---|---|---|
| Hardware cost | Rs 800-900 | Rs 3,000-8,000 |
| Monthly subscription | None | Rs 0-200/month (some require cloud plans) |
| Customization | Full control | Limited to app features |
| Multi-channel | Add Rs 180 per channel | Often single-channel only |
| Data ownership | 100% local | Cloud-dependent |
| Learning value | Immense | None |
The DIY route costs roughly one-fourth of a commercial unit and teaches you electronics, embedded programming, and IoT concepts along the way.
Troubleshooting
Reading is always zero: Check that the CT is clamped around a single wire, not the entire cable. Verify the burden resistor is connected. Check that you are using an ADC1 pin (ADC2 pins do not work when WiFi is active on ESP32).
Reading fluctuates wildly: Add a 10uF capacitor across the CT output for additional filtering. Ensure the bias voltage is stable at 1.65V using a multimeter. Increase the sample count in calcIrms() from 1480 to 2960.
Reading is half of expected: You may have the CT clamped around a cable with live and neutral together, causing partial cancellation. Separate the wires.
OLED shows nothing: Check I2C address. Some SSD1306 modules use 0x3D instead of 0x3C. Run an I2C scanner sketch to confirm.
WiFi keeps disconnecting: Add a reconnection check in loop():
if (WiFi.status() != WL_CONNECTED) {
WiFi.reconnect();
}
What You Will Learn
This project is not just about saving money on electricity. Building it teaches you:
- Analog signal processing — biasing, burden resistors, AC-to-DC conversion
- RMS calculation — the math behind real AC measurement
- ESP32 ADC usage — and its quirks (non-linearity, noise)
- Web server programming — serving HTML/CSS/JS from a microcontroller
- IoT architecture — MQTT, cloud integration, real-time dashboards
- Power engineering basics — voltage, current, power factor, kWh, tariff slabs
Next Steps
Once your basic monitor is running reliably:
- Calibrate carefully using multiple known loads
- Install it at your distribution board to monitor whole-house consumption
- Add MQTT and connect it to Home Assistant for historical graphs
- Add a second CT on your AC or geyser circuit to identify the biggest consumers
- Set up notifications when daily consumption exceeds your target
- Compare your readings with your actual electricity bill to validate accuracy
The components used in this project — ESP32 DevKit, SCT-013-000 current sensor, SSD1306 OLED display, and all the passive components — are available at wavtron.in. If you build this project, we would love to hear about your results.
Happy monitoring, and may your electricity bills be ever lower.



