#define BLYNK_TEMPLATE_ID "TMPL3nUpWRIwo"
#define BLYNK_TEMPLATE_NAME "smart energy meter"
#define BLYNK_AUTH_TOKEN "qjXdebYGjI6bG0Cm9juz1WOE6CM1a6EH"
#define BLYNK_PRINT Serial
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <WidgetRTC.h>
char ssid[] = "Wokwi-GUEST";
char pass[] = "";
// ================================================================
// PIN DEFINITIONS
// ================================================================
#define VOLT_PIN 34
#define CURR_IN_PIN 35
#define CURR_OUT_PIN 32
#define RELAY_PIN 26
#define LED_PIN 27
#define BUZZER_PIN 25
#define TAMPER_PIN 33
// ================================================================
// SYSTEM SETTINGS
// ================================================================
#define OVERLOAD_CURRENT_LIMIT 20.0
#define THEFT_THRESHOLD_DIFF 2.0
#define LOW_PF_SURCHARGE_RATE 0.15
// 30 days
#define MONTH_IN_MS 10000UL // for 1 month : 259000000UL
// Voltage protection
#define MIN_VOLTAGE_LIMIT 180.0
#define MAX_VOLTAGE_LIMIT 260.0
// ================================================================
// TARIFF SLABS
// ================================================================
#define SLAB1_LIMIT 100.0
#define SLAB2_LIMIT 200.0
#define SLAB3_LIMIT 300.0
#define SLAB1_RATE 4.0
#define SLAB2_RATE 5.5
#define SLAB3_RATE 7.0
#define SLAB4_RATE 8.5
// ================================================================
// LCD / BLYNK / RTC
// ================================================================
LiquidCrystal_I2C lcd(0x27, 16, 2);
BlynkTimer timer;
WidgetRTC rtc;
// ================================================================
// ENERGY VARIABLES
// ================================================================
double totalEnergy_kWh = 0.0;
double dailyEnergy_kWh = 0.0;
unsigned long lastTime = 0;
unsigned long startTime = 0;
unsigned long dayStartTime = 0;
int lcdScreenPage = 0;
unsigned long lastDisplaySwitch = 0;
// ================================================================
// METER VARIABLES
// ================================================================
float voltage = 0.0;
float activeCurrent = 0.0;
float estimatedBill = 0.0;
float predictedMonthlyBill = 0.0;
float powerFactor = 0.95;
String pfStatus = "GOOD";
String overloadStatus = "NORMAL";
// ================================================================
// MANUAL RELAY
// V9
// ================================================================
bool manualRelayState = true;
// ================================================================
// FAULT NOTIFICATION
// ================================================================
bool lastFaultState = false;
// ================================================================
// BUDGET
// V13
// ================================================================
float billBudgetLimit = 500.0;
bool budgetAlertSent = false;
// ================================================================
// PEAK USAGE
// ================================================================
float maxPower_kW = 0.0;
String peakTimeRangeString = "12AM-1AM";
// ================================================================
// TARIFF
// V14
// ================================================================
String currentTariffSlab = "SLAB 1";
// ================================================================
// PAYMENT ID
// V15 = Payment ID
// V16 = Payment Status
// ================================================================
bool paymentIDGenerated = false;
bool paymentPending = false;
String paymentID = "";
String paymentStatus = "PAYMENT NOT DUE";
unsigned int billingCycleNumber = 1;
// ================================================================
// BLYNK CONNECTED
// ================================================================
BLYNK_CONNECTED()
{
rtc.begin();
// Send current payment information after reconnection
Blynk.virtualWrite(V15, paymentID);
Blynk.virtualWrite(V16, paymentStatus);
}
// ================================================================
// V9 - MANUAL RELAY CONTROL
// ================================================================
BLYNK_WRITE(V9)
{
manualRelayState = param.asInt();
Serial.print("Manual Relay: ");
if (manualRelayState)
{
Serial.println("ON");
}
else
{
Serial.println("OFF");
}
}
// ================================================================
// V10 - BILLING RESET
// ================================================================
BLYNK_WRITE(V10)
{
if (param.asInt() == 1)
{
unsigned long currentMillis = millis();
// ------------------------------------------------------------
// CHECK WHETHER 30 DAYS HAVE COMPLETED
// ------------------------------------------------------------
if (currentMillis - startTime >= MONTH_IN_MS)
{
// Reset energy
totalEnergy_kWh = 0.0;
dailyEnergy_kWh = 0.0;
// Reset bill
estimatedBill = 0.0;
// Reset budget alert
budgetAlertSent = false;
// Reset tariff
currentTariffSlab = "SLAB 1";
// ----------------------------------------------------------
// RESET PAYMENT INFORMATION
// ----------------------------------------------------------
paymentIDGenerated = false;
paymentPending = false;
paymentID = "";
paymentStatus = "PAYMENT NOT DUE";
// ----------------------------------------------------------
// NEXT BILLING CYCLE
// ----------------------------------------------------------
billingCycleNumber++;
startTime = currentMillis;
Serial.println();
Serial.println("================================");
Serial.println("BILL RESET SUCCESSFUL");
Serial.println("NEW BILLING CYCLE STARTED");
Serial.println("PAYMENT ID CLEARED");
Serial.println("================================");
if (Blynk.connected())
{
Blynk.virtualWrite(V15, "");
Blynk.virtualWrite(V16, paymentStatus);
Blynk.logEvent(
"fault_alert",
"Payment cycle completed. New 30-day billing cycle started."
);
}
}
// ------------------------------------------------------------
// RESET BEFORE 30 DAYS
// ------------------------------------------------------------
else
{
unsigned long elapsedMs =
currentMillis - startTime;
unsigned long remainingMs =
MONTH_IN_MS - elapsedMs;
int remainingDays =
(remainingMs / 86400000UL) + 1;
String warnMsg =
"WARNING: You can't reset the bill before 30 days! Remaining: "
+ String(remainingDays)
+ " days.";
Serial.println(warnMsg);
if (Blynk.connected())
{
Blynk.logEvent(
"fault_alert",
warnMsg
);
}
}
}
}
// ================================================================
// V13 - BILL BUDGET
// ================================================================
BLYNK_WRITE(V13)
{
billBudgetLimit = param.asFloat();
budgetAlertSent = false;
Serial.print("New budget limit Rs. ");
Serial.println(billBudgetLimit, 2);
}
// ================================================================
// REALISTIC SLAB TARIFF CALCULATION
// ================================================================
float calculateSlabBill(float units)
{
float bill = 0.0;
// ------------------------------------------------------------
// SLAB 1: 0-100 units
// ------------------------------------------------------------
if (units <= SLAB1_LIMIT)
{
bill = units * SLAB1_RATE;
}
// ------------------------------------------------------------
// SLAB 2: 101-200 units
// ------------------------------------------------------------
else if (units <= SLAB2_LIMIT)
{
bill =
(SLAB1_LIMIT * SLAB1_RATE)
+
((units - SLAB1_LIMIT) * SLAB2_RATE);
}
// ------------------------------------------------------------
// SLAB 3: 201-300 units
// ------------------------------------------------------------
else if (units <= SLAB3_LIMIT)
{
bill =
(SLAB1_LIMIT * SLAB1_RATE)
+
((SLAB2_LIMIT - SLAB1_LIMIT) * SLAB2_RATE)
+
((units - SLAB2_LIMIT) * SLAB3_RATE);
}
// ------------------------------------------------------------
// SLAB 4: ABOVE 300 units
// ------------------------------------------------------------
else
{
bill =
(SLAB1_LIMIT * SLAB1_RATE)
+
((SLAB2_LIMIT - SLAB1_LIMIT) * SLAB2_RATE)
+
((SLAB3_LIMIT - SLAB2_LIMIT) * SLAB3_RATE)
+
((units - SLAB3_LIMIT) * SLAB4_RATE);
}
return bill;
}
// ================================================================
// UPDATE TARIFF SLAB
// ================================================================
void updateTariffSlab()
{
if (totalEnergy_kWh <= SLAB1_LIMIT)
{
currentTariffSlab = "SLAB 1";
}
else if (totalEnergy_kWh <= SLAB2_LIMIT)
{
currentTariffSlab = "SLAB 2";
}
else if (totalEnergy_kWh <= SLAB3_LIMIT)
{
currentTariffSlab = "SLAB 3";
}
else
{
currentTariffSlab = "SLAB 4";
}
}
// ================================================================
// GENERATE PAYMENT ID AFTER 30 DAYS
// ================================================================
void generatePaymentID()
{
// Prevent duplicate Payment ID generation
if (paymentIDGenerated)
{
return;
}
// ------------------------------------------------------------
// FINAL BILL
// ------------------------------------------------------------
float finalBill = estimatedBill;
// ------------------------------------------------------------
// GET YEAR FROM RTC
// ------------------------------------------------------------
int currentYear = year();
// ------------------------------------------------------------
// GENERATE PAYMENT ID
//
// Example:
//
// SEM-2026-1-1300
//
// SEM = Smart Energy Meter
// 2026 = Year
// 1 = Billing cycle
// 1300 = Bill amount
// ------------------------------------------------------------
paymentID =
"SEM-"
+ String(currentYear)
+ "-"
+ String(billingCycleNumber)
+ "-"
+ String((int)finalBill);
// ------------------------------------------------------------
// PAYMENT STATUS
// ------------------------------------------------------------
paymentIDGenerated = true;
paymentPending = true;
paymentStatus = "PAYMENT DUE";
// ------------------------------------------------------------
// SERIAL MONITOR
// ------------------------------------------------------------
Serial.println();
Serial.println("==========================================");
Serial.println("30-DAY BILLING CYCLE COMPLETED");
Serial.println("==========================================");
Serial.print("Energy : ");
Serial.print(totalEnergy_kWh, 2);
Serial.println(" kWh");
Serial.print("Tariff Slab : ");
Serial.println(currentTariffSlab);
Serial.print("Final Bill : Rs.");
Serial.println(finalBill, 2);
Serial.print("Payment ID : ");
Serial.println(paymentID);
Serial.print("Payment Status: ");
Serial.println(paymentStatus);
Serial.println("==========================================");
Serial.println();
// ------------------------------------------------------------
// SEND TO BLYNK
// ------------------------------------------------------------
if (Blynk.connected())
{
Blynk.virtualWrite(V15, paymentID);
Blynk.virtualWrite(V16, paymentStatus);
Blynk.logEvent(
"fault_alert",
"30-day bill generated. Payment ID: " + paymentID
);
}
}
// ================================================================
// SEND SENSOR DATA TO BLYNK
// ================================================================
void sendSensorDataToBlynk()
{
if (Blynk.connected())
{
// Basic meter data
Blynk.virtualWrite(V0, voltage);
Blynk.virtualWrite(V1, activeCurrent);
Blynk.virtualWrite(V2, totalEnergy_kWh);
Blynk.virtualWrite(V3, estimatedBill);
// Fault status
Blynk.virtualWrite(V4, overloadStatus);
// Expected bill
Blynk.virtualWrite(V5, predictedMonthlyBill);
// Daily energy
Blynk.virtualWrite(V6, dailyEnergy_kWh);
// Power factor
Blynk.virtualWrite(V7, powerFactor);
Blynk.virtualWrite(V8, pfStatus);
// Peak usage
Blynk.virtualWrite(V11, maxPower_kW);
Blynk.virtualWrite(V12, peakTimeRangeString);
// Tariff
Blynk.virtualWrite(V14, currentTariffSlab);
// Payment
Blynk.virtualWrite(V15, paymentID);
Blynk.virtualWrite(V16, paymentStatus);
}
// ------------------------------------------------------------
// SERIAL MONITOR
// ------------------------------------------------------------
Serial.println("==========================================");
Serial.print("Voltage : ");
Serial.print(voltage, 1);
Serial.println(" V");
Serial.print("Current : ");
Serial.print(activeCurrent, 2);
Serial.println(" A");
Serial.print("Energy : ");
Serial.print(totalEnergy_kWh, 3);
Serial.println(" kWh");
Serial.print("Tariff Slab : ");
Serial.println(currentTariffSlab);
Serial.print("Current Bill : Rs.");
Serial.println(estimatedBill, 2);
Serial.print("Expected Bill : Rs.");
Serial.println(predictedMonthlyBill, 2);
Serial.print("Budget Limit : Rs.");
Serial.println(billBudgetLimit, 2);
Serial.print("Payment : ");
Serial.println(paymentStatus);
if (paymentIDGenerated)
{
Serial.print("Payment ID : ");
Serial.println(paymentID);
}
Serial.println("==========================================");
}
// ================================================================
// SETUP
// ================================================================
void setup()
{
Serial.begin(115200);
delay(500);
// ------------------------------------------------------------
// PIN CONFIGURATION
// ------------------------------------------------------------
pinMode(RELAY_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(TAMPER_PIN, INPUT_PULLUP);
// ------------------------------------------------------------
// INITIAL OUTPUTS
// ------------------------------------------------------------
digitalWrite(RELAY_PIN, HIGH);
digitalWrite(LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
// ------------------------------------------------------------
// LCD
// ------------------------------------------------------------
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Smart Energy");
lcd.setCursor(0, 1);
lcd.print("Meter Starting");
delay(2000);
lcd.clear();
// ------------------------------------------------------------
// BLYNK
// ------------------------------------------------------------
Serial.println("Connecting to Blynk Server...");
Blynk.begin(
BLYNK_AUTH_TOKEN,
ssid,
pass,
"sgp1.blynk.cloud",
80
);
// ------------------------------------------------------------
// TIMER
// ------------------------------------------------------------
timer.setInterval(
1000L,
sendSensorDataToBlynk
);
// ------------------------------------------------------------
// INITIAL TIME
// ------------------------------------------------------------
lastTime = millis();
startTime = millis();
dayStartTime = millis();
}
// ================================================================
// MAIN LOOP
// ================================================================
void loop()
{
Blynk.run();
timer.run();
unsigned long currentTime = millis();
// ============================================================
// ENERGY TIME CALCULATION
// ============================================================
double elapsedTimeHours =
(currentTime - lastTime) / 3600000.0;
lastTime = currentTime;
// ============================================================
// 24-HOUR DAILY ENERGY & PEAK RESET
// ============================================================
if (currentTime - dayStartTime >= 86400000UL)
{
dailyEnergy_kWh = 0.0;
maxPower_kW = 0.0;
peakTimeRangeString = "12AM-1AM";
dayStartTime = currentTime;
}
// ============================================================
// 1. READ SENSORS
// ============================================================
int voltRaw = analogRead(VOLT_PIN);
int currInRaw = analogRead(CURR_IN_PIN);
int currOutRaw = analogRead(CURR_OUT_PIN);
// ============================================================
// SENSOR CONVERSION
// ============================================================
voltage =
(voltRaw / 4095.0) * 415.0;
float currentIn =
(currInRaw / 4095.0) * 25.0;
float currentOut =
(currOutRaw / 4095.0) * 25.0;
activeCurrent = currentIn;
// ============================================================
// 2. DYNAMIC POWER FACTOR
// ============================================================
if (activeCurrent > 15.0)
{
powerFactor = 0.80;
}
else if (activeCurrent > 2.0)
{
powerFactor = 0.88;
}
else if (activeCurrent > 0.1)
{
powerFactor = 0.96;
}
else
{
powerFactor = 1.00;
}
// ============================================================
// PF STATUS
// ============================================================
if (powerFactor > 0.95)
{
pfStatus = "Perfect";
}
else if (powerFactor >= 0.85)
{
pfStatus = "GOOD";
}
else if (powerFactor >= 0.75)
{
pfStatus = "FAIR (PENALTY APPLIED)";
}
else
{
pfStatus = "POOR (HIGH SURCHARGE!)";
}
// ============================================================
// 3. POWER CALCULATION
// ============================================================
float power_kW =
(voltage * activeCurrent * powerFactor)
/ 1000.0;
// ============================================================
// PEAK USAGE RANGE CALCULATION
// ============================================================
if (power_kW > maxPower_kW &&
power_kW > 0.01)
{
maxPower_kW = power_kW;
// Get current hour from RTC
int currentHour = hour();
int nextHour =
(currentHour + 1) % 24;
// ----------------------------------------------------------
// START HOUR
// ----------------------------------------------------------
int startHour12;
if (currentHour % 12 == 0)
{
startHour12 = 12;
}
else
{
startHour12 =
currentHour % 12;
}
String startAmPm;
if (currentHour >= 12)
{
startAmPm = "PM";
}
else
{
startAmPm = "AM";
}
// ----------------------------------------------------------
// END HOUR
// ----------------------------------------------------------
int endHour12;
if (nextHour % 12 == 0)
{
endHour12 = 12;
}
else
{
endHour12 =
nextHour % 12;
}
String endAmPm;
if (nextHour >= 12)
{
endAmPm = "PM";
}
else
{
endAmPm = "AM";
}
// Example: 7PM-8PM
peakTimeRangeString =
String(startHour12)
+ startAmPm
+ "-"
+ String(endHour12)
+ endAmPm;
}
// ============================================================
// ENERGY CALCULATION
// ============================================================
double energyIncrement =
power_kW * elapsedTimeHours;
totalEnergy_kWh +=
energyIncrement;
dailyEnergy_kWh +=
energyIncrement;
// ============================================================
// UPDATE TARIFF
// =================================================
updateTariffSlab();
// ============================================================
// REALISTIC TARIFF BILL
// ============================================================
float baseBill =
calculateSlabBill(
totalEnergy_kWh
);
// ============================================================
// PF SURCHARGE
// ============================================================
if (powerFactor < 0.90)
{
estimatedBill =
baseBill *
(1.0 + LOW_PF_SURCHARGE_RATE);
}
else
{
estimatedBill =
baseBill;
}
// ============================================================
// 4. PREDICTED MONTHLY BILL
// ============================================================
double projected30DayEnergy_kWh =
power_kW * 720.0;
double basePredictedBill =
calculateSlabBill(
projected30DayEnergy_kWh
);
if (powerFactor < 0.90)
{
predictedMonthlyBill =
basePredictedBill *
(1.0 + LOW_PF_SURCHARGE_RATE);
}
else
{
predictedMonthlyBill =
basePredictedBill;
}
// ============================================================
// BUDGET CONTROL
// ============================================================
if (estimatedBill >= billBudgetLimit &&
!budgetAlertSent &&
billBudgetLimit > 0)
{
String alertMsg =
"BUDGET ALERT: Bill (Rs."
+ String(estimatedBill, 2)
+ ") exceeded limit Rs."
+ String(billBudgetLimit, 2)
+ "!";
if (Blynk.connected())
{
Blynk.logEvent(
"fault_alert",
alertMsg
);
}
budgetAlertSent = true;
}
// ============================================================
// 30-DAY PAYMENT ID GENERATION
// ============================================================
if ((currentTime - startTime >= MONTH_IN_MS) &&
!paymentIDGenerated)
{
generatePaymentID();
}
// ============================================================
// 5. PROTECTION LOGIC
// ============================================================
bool isUnderVoltage =
(voltage > 10.0 &&
voltage < MIN_VOLTAGE_LIMIT);
bool isOverVoltage =
(voltage > MAX_VOLTAGE_LIMIT);
bool isOverloaded =
(activeCurrent > OVERLOAD_CURRENT_LIMIT);
bool isBypassed =
(abs(currentIn - currentOut) >
THEFT_THRESHOLD_DIFF);
bool isTampered =
(digitalRead(TAMPER_PIN) == HIGH);
bool faultDetected = false;
String statusMessage = "";
// ------------------------------------------------------------
// UNDER VOLTAGE
// ------------------------------------------------------------
if (isUnderVoltage)
{
faultDetected = true;
statusMessage =
"UNDER VOLTAGE!";
overloadStatus =
"LOW VOLTAGE TRIP";
}
// ------------------------------------------------------------
// OVER VOLTAGE
// ------------------------------------------------------------
else if (isOverVoltage)
{
faultDetected = true;
statusMessage =
"OVER VOLTAGE!";
overloadStatus =
"HIGH VOLTAGE TRIP";
}
// ------------------------------------------------------------
// OVERLOAD
// ------------------------------------------------------------
else if (isOverloaded)
{
faultDetected = true;
statusMessage =
"OVERLOAD TRIP!";
overloadStatus =
"OVERLOAD!";
}
// ------------------------------------------------------------
// THEFT / BYPASS
// ------------------------------------------------------------
else if (isBypassed)
{
faultDetected = true;
statusMessage =
"LINE THEFT DET!";
overloadStatus =
"THEFT DETECTED";
}
// ------------------------------------------------------------
// TAMPER
// ------------------------------------------------------------
else if (isTampered)
{
faultDetected = true;
statusMessage =
"TAMPER DETECTED!";
overloadStatus =
"TAMPERED";
}
// ------------------------------------------------------------
// NORMAL
// ------------------------------------------------------------
else
{
overloadStatus =
"NORMAL";
}
// ============================================================
// FAULT NOTIFICATION
// ============================================================
if (faultDetected &&
!lastFaultState)
{
if (Blynk.connected())
{
Blynk.logEvent(
"fault_alert",
String("FAULT TRIP: ") +
statusMessage
);
}
lastFaultState = true;
}
else if (!faultDetected)
{
lastFaultState = false;
}
// ============================================================
// 6. ACTUATORS
// ============================================================
if (faultDetected ||
!manualRelayState)
{
digitalWrite(
RELAY_PIN,
LOW
);
digitalWrite(
LED_PIN,
LOW
);
digitalWrite(
BUZZER_PIN,
HIGH
);
}
else
{
digitalWrite(
RELAY_PIN,
HIGH
);
digitalWrite(
LED_PIN,
HIGH
);
digitalWrite(
BUZZER_PIN,
LOW
);
}
// ============================================================
// 7. LCD PAGE CYCLING
// ============================================================
if (currentTime -
lastDisplaySwitch > 3000)
{
lcdScreenPage =
(lcdScreenPage + 1) % 3;
lastDisplaySwitch =
currentTime;
lcd.clear();
}
// ============================================================
// FAULT DISPLAY
// ============================================================
if (faultDetected)
{
lcd.setCursor(0, 0);
lcd.print(
"!! FAULT TRIP !!"
);
lcd.setCursor(0, 1);
lcd.print(
statusMessage
);
}
// ============================================================
// LCD PAGE 0 - VOLTAGE/CURRENT
// ============================================================
else if (lcdScreenPage == 0)
{
lcd.setCursor(0, 0);
lcd.print("Volt: ");
lcd.print(
voltage,
1
);
lcd.print(" V ");
lcd.setCursor(0, 1);
lcd.print("Curr: ");
lcd.print(
activeCurrent,
2
);
lcd.print(" A ");
}
// ============================================================
// LCD PAGE 1 - ENERGY/BILL
// ============================================================
else if (lcdScreenPage == 1)
{
lcd.setCursor(0, 0);
lcd.print("Energy: ");
lcd.print(
totalEnergy_kWh,
3
);
lcd.print("kWh");
lcd.setCursor(0, 1);
lcd.print("Bill: Rs.");
lcd.print(
estimatedBill,
2
);
lcd.print(" ");
}
// ============================================================
// LCD PAGE 2 - PEAK POWER
// ============================================================
else if (lcdScreenPage == 2)
{
lcd.setCursor(0, 0);
lcd.print("Peak:");
lcd.print(
peakTimeRangeString
);
lcd.setCursor(0, 1);
lcd.print("Pwr: ");
lcd.print(
maxPower_kW,
2
);
lcd.print(" kW");
}
delay(200);
}