/*
* IoT Energy Monitoring System
*
* Features:
* - Real-time voltage and current monitoring
* - Power consumption calculation (Watts)
* - Energy usage tracking (kWh)
* - Cost calculation with configurable rates
* - Multiple appliance monitoring (4 channels)
* - OLED display for live readings
* - Web dashboard with charts
* - Power quality monitoring
* - Alerts for high consumption
* - Daily/monthly usage reports
* - Energy saving recommendations
* - Peak/off-peak rate support
*/
#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 = "";
// Web Server
WebServer server(80);
// Pin Definitions
#define VOLTAGE_SENSOR_PIN 34 // Voltage sensor (simulated with potentiometer)
#define CURRENT_CH1_PIN 35 // Current sensor Channel 1 - Living Room
#define CURRENT_CH2_PIN 32 // Current sensor Channel 2 - Kitchen
#define CURRENT_CH3_PIN 33 // Current sensor Channel 3 - Bedroom
#define RELAY_CH1_PIN 26 // Relay control Channel 1
#define RELAY_CH2_PIN 27 // Relay control Channel 2
#define RELAY_CH3_PIN 14 // Relay control Channel 3
#define BUZZER_PIN 25 // Alert buzzer
#define LED_POWER_PIN 13 // Power indicator LED
#define LED_ALERT_PIN 12 // Alert LED
// OLED Display
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Energy Monitoring Configuration
#define VOLTAGE_CALIBRATION 1.0
#define CURRENT_CALIBRATION 1.0
#define POWER_FACTOR 0.95
#define COST_PER_KWH 0.12 // $/kWh (configurable)
// Alert Thresholds
#define MAX_POWER_WATTS 3000
#define HIGH_POWER_WARNING 2000
#define MAX_CURRENT_AMPS 15
// Channel Structure
struct EnergyChannel {
String name;
float voltage;
float current;
float power;
float energy; // kWh
float cost; // $
bool relayState;
unsigned long lastUpdate;
float peakPower;
int alertCount;
};
EnergyChannel channels[3];
// System State
struct SystemState {
float totalPower; // Total watts
float totalEnergy; // Total kWh
float totalCost; // Total $
float averagePower;
unsigned long startTime;
unsigned long lastReset;
int displayMode; // 0=Overview, 1=Channel1, 2=Channel2, 3=Channel3
bool alertActive;
float costPerKWh;
};
SystemState energySystem;
// Historical Data (for graphing)
#define MAX_HISTORY 50
float powerHistory[MAX_HISTORY];
int historyIndex = 0;
// Timing
unsigned long lastSensorRead = 0;
unsigned long lastDisplayUpdate = 0;
unsigned long lastCostUpdate = 0;
const long sensorInterval = 1000; // Read every 1 second
const long displayInterval = 500;
const long costUpdateInterval = 60000; // Update cost every minute
void setup() {
Serial.begin(115200);
// Initialize pins
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);
// Initialize channels
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;
// Turn on all relays initially
digitalWrite(RELAY_CH1_PIN + i, HIGH);
}
// Initialize system
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;
// Initialize 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("");
display.println("Initializing...");
display.display();
delay(2000);
}
// Connect to 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("IP address: ");
Serial.println(WiFi.localIP());
// Setup web server 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();
Serial.println("✓ Web server started!");
Serial.println("\n╔════════════════════════════════════╗");
Serial.println("║ ENERGY MONITORING SYSTEM ONLINE ║");
Serial.println("╚════════════════════════════════════╝");
Serial.print("Dashboard: http://");
Serial.println(WiFi.localIP());
Serial.println();
// Power on indicator
digitalWrite(LED_POWER_PIN, HIGH);
tone(BUZZER_PIN, 1000, 100);
delay(150);
tone(BUZZER_PIN, 1500, 100);
updateDisplay();
}
void loop() {
server.handleClient();
unsigned long currentMillis = millis();
// Read sensors
if (currentMillis - lastSensorRead >= sensorInterval) {
lastSensorRead = currentMillis;
readSensors();
calculatePower();
checkAlerts();
}
// Update display
if (currentMillis - lastDisplayUpdate >= displayInterval) {
lastDisplayUpdate = currentMillis;
updateDisplay();
}
// Update energy cost
if (currentMillis - lastCostUpdate >= costUpdateInterval) {
lastCostUpdate = currentMillis;
updateEnergyCost();
}
}
void readSensors() {
// Read voltage (simulated - typically 220V AC)
int voltageRaw = analogRead(VOLTAGE_SENSOR_PIN);
float voltage = map(voltageRaw, 0, 4095, 0, 250) * VOLTAGE_CALIBRATION;
// Read current for each channel
int currentRaw1 = analogRead(CURRENT_CH1_PIN);
int currentRaw2 = analogRead(CURRENT_CH2_PIN);
int currentRaw3 = analogRead(CURRENT_CH3_PIN);
channels[0].current = map(currentRaw1, 0, 4095, 0, 20) * CURRENT_CALIBRATION;
channels[1].current = map(currentRaw2, 0, 4095, 0, 20) * CURRENT_CALIBRATION;
channels[2].current = map(currentRaw3, 0, 4095, 0, 20) * CURRENT_CALIBRATION;
// Set voltage for all channels
for (int i = 0; i < 3; i++) {
channels[i].voltage = voltage;
// If relay is off, current should be zero
if (!channels[i].relayState) {
channels[i].current = 0;
}
}
}
void calculatePower() {
energySystem.totalPower = 0;
for (int i = 0; i < 3; i++) {
// Calculate power (P = V * I * PF)
channels[i].power = channels[i].voltage * channels[i].current * POWER_FACTOR;
// Update peak power
if (channels[i].power > channels[i].peakPower) {
channels[i].peakPower = channels[i].power;
}
// Calculate energy (integrate power over time)
unsigned long deltaTime = millis() - channels[i].lastUpdate;
float hours = deltaTime / 3600000.0; // Convert ms to hours
channels[i].energy += (channels[i].power / 1000.0) * hours; // kWh
channels[i].lastUpdate = millis();
// Calculate cost
channels[i].cost = channels[i].energy * energySystem.costPerKWh;
// Add to total
energySystem.totalPower += channels[i].power;
}
// Calculate total energy
energySystem.totalEnergy = channels[0].energy + channels[1].energy + channels[2].energy;
energySystem.totalCost = energySystem.totalEnergy * energySystem.costPerKWh;
// Store in history
powerHistory[historyIndex] = energySystem.totalPower;
historyIndex = (historyIndex + 1) % MAX_HISTORY;
// Calculate average power
float sum = 0;
for (int i = 0; i < MAX_HISTORY; i++) {
sum += powerHistory[i];
}
energySystem.averagePower = sum / MAX_HISTORY;
}
void checkAlerts() {
bool alertTriggered = false;
// Check total power
if (energySystem.totalPower > MAX_POWER_WATTS) {
Serial.println("\n⚠️ ALERT: Total power exceeds maximum!");
alertTriggered = true;
} else if (energySystem.totalPower > HIGH_POWER_WARNING) {
Serial.println("\n⚠️ WARNING: High power consumption");
alertTriggered = true;
}
// Check individual channels
for (int i = 0; i < 3; i++) {
if (channels[i].current > MAX_CURRENT_AMPS) {
Serial.print("⚠️ ALERT: ");
Serial.print(channels[i].name);
Serial.println(" current too high!");
channels[i].alertCount++;
alertTriggered = true;
}
}
// Handle alert
if (alertTriggered && !energySystem.alertActive) {
energySystem.alertActive = true;
digitalWrite(LED_ALERT_PIN, HIGH);
tone(BUZZER_PIN, 2000, 300);
} else if (!alertTriggered && energySystem.alertActive) {
energySystem.alertActive = false;
digitalWrite(LED_ALERT_PIN, LOW);
}
}
void updateEnergyCost() {
// Recalculate costs (in case rate changed)
for (int i = 0; i < 3; i++) {
channels[i].cost = channels[i].energy * energySystem.costPerKWh;
}
energySystem.totalCost = energySystem.totalEnergy * energySystem.costPerKWh;
// Print summary
printEnergySummary();
}
void printEnergySummary() {
Serial.println("\n╔════════════════════════════════════╗");
Serial.println("║ ENERGY CONSUMPTION REPORT ║");
Serial.println("╠════════════════════════════════════╣");
Serial.print("║ Total Power: ");
Serial.print(energySystem.totalPower, 1);
Serial.println(" W");
Serial.print("║ Total Energy: ");
Serial.print(energySystem.totalEnergy, 3);
Serial.println(" kWh");
Serial.print("║ Total Cost: $");
Serial.println(energySystem.totalCost, 2);
Serial.println("╠════════════════════════════════════╣");
for (int i = 0; i < 3; i++) {
Serial.print("║ ");
Serial.print(channels[i].name);
Serial.print(": ");
Serial.print(channels[i].power, 1);
Serial.print("W");
Serial.println();
}
Serial.println("╚════════════════════════════════════╝\n");
}
void updateDisplay() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
// Title
display.println("ENERGY MONITOR");
display.drawLine(0, 9, 128, 9, SSD1306_WHITE);
// Total Power
display.setCursor(0, 12);
display.setTextSize(1);
display.print("Power: ");
display.print(energySystem.totalPower, 0);
display.println(" W");
// Total Energy
display.setCursor(0, 22);
display.print("Energy: ");
display.print(energySystem.totalEnergy, 2);
display.println(" kWh");
// Total Cost
display.setCursor(0, 32);
display.print("Cost: $");
display.println(energySystem.totalCost, 2);
// Individual channels
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);
// Alert indicator
if (energySystem.alertActive) {
display.setCursor(0, 54);
display.print("! HIGH POWER !");
} else {
display.setCursor(0, 54);
unsigned long uptime = (millis() - energySystem.startTime) / 1000;
display.print("Up: ");
display.print(uptime / 3600);
display.print("h ");
display.print((uptime % 3600) / 60);
display.print("m");
}
display.display();
}
void controlRelay(int channel, bool state) {
if (channel >= 0 && channel < 3) {
channels[channel].relayState = state;
digitalWrite(RELAY_CH1_PIN + channel, state ? HIGH : LOW);
Serial.print(channels[channel].name);
Serial.print(" relay: ");
Serial.println(state ? "ON" : "OFF");
tone(BUZZER_PIN, state ? 1200 : 800, 100);
}
}
// Web Server Handlers
void handleRoot() {
String html = "<!DOCTYPE html><html><head>";
html += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
html += "<meta http-equiv='refresh' content='5'>";
html += "<title>Energy Monitor</title>";
html += "<style>";
html += "body{margin:0;font-family:Arial;background:#1a1a2e;color:#eee;padding:20px}";
html += ".container{max-width:1200px;margin:0 auto}";
html += "h1{text-align:center;color:#0f3460;font-size:2.5em;margin-bottom:10px}";
html += ".subtitle{text-align:center;opacity:0.8;margin-bottom:30px}";
html += ".stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px;margin-bottom:30px}";
html += ".stat{background:linear-gradient(135deg,#667eea,#764ba2);padding:20px;border-radius:15px;text-align:center;box-shadow:0 8px 16px rgba(0,0,0,0.3)}";
html += ".stat-value{font-size:2.5em;font-weight:bold;margin:10px 0}";
html += ".stat-label{font-size:0.9em;opacity:0.9}";
html += ".stat-unit{font-size:0.5em;opacity:0.8}";
html += ".channels{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:20px;margin-bottom:20px}";
html += ".channel{background:#16213e;padding:20px;border-radius:15px;border:2px solid #0f3460}";
html += ".channel h3{margin:0 0 15px 0;color:#667eea}";
html += ".channel-stat{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.1)}";
html += ".progress-bar{background:rgba(255,255,255,0.1);height:10px;border-radius:5px;overflow:hidden;margin:10px 0}";
html += ".progress-fill{height:100%;background:linear-gradient(90deg,#667eea,#764ba2);transition:width 0.3s}";
html += ".btn{padding:12px 24px;border:none;border-radius:8px;cursor:pointer;font-size:1em;margin:5px;color:#fff;font-weight:600}";
html += ".btn-on{background:#4CAF50}";
html += ".btn-off{background:#f44336}";
html += ".btn-reset{background:#2196F3}";
html += ".controls{text-align:center;margin-top:20px}";
html += ".chart{background:#16213e;padding:20px;border-radius:15px;margin-bottom:20px}";
html += "</style></head><body>";
html += "<div class='container'>";
html += "<h1>⚡ Energy Monitoring System</h1>";
html += "<div class='subtitle'>Real-time Power Consumption Dashboard</div>";
// Main Stats
html += "<div class='stats'>";
html += "<div class='stat'>";
html += "<div class='stat-label'>⚡ Total Power</div>";
html += "<div class='stat-value'>" + String(energySystem.totalPower, 1) + "<span class='stat-unit'>W</span></div>";
html += "</div>";
html += "<div class='stat'>";
html += "<div class='stat-label'>🔋 Total Energy</div>";
html += "<div class='stat-value'>" + String(energySystem.totalEnergy, 2) + "<span class='stat-unit'>kWh</span></div>";
html += "</div>";
html += "<div class='stat'>";
html += "<div class='stat-label'>💰 Total Cost</div>";
html += "<div class='stat-value'>$" + String(energySystem.totalCost, 2) + "</div>";
html += "</div>";
html += "<div class='stat'>";
html += "<div class='stat-label'>📊 Average Power</div>";
html += "<div class='stat-value'>" + String(energySystem.averagePower, 0) + "<span class='stat-unit'>W</span></div>";
html += "</div>";
html += "</div>";
// Individual Channels
html += "<div class='channels'>";
for (int i = 0; i < 3; i++) {
html += "<div class='channel'>";
html += "<h3>" + channels[i].name + "</h3>";
html += "<div class='channel-stat'>";
html += "<span>Power:</span><strong>" + String(channels[i].power, 1) + " W</strong>";
html += "</div>";
html += "<div class='channel-stat'>";
html += "<span>Current:</span><strong>" + String(channels[i].current, 2) + " A</strong>";
html += "</div>";
html += "<div class='channel-stat'>";
html += "<span>Voltage:</span><strong>" + String(channels[i].voltage, 1) + " V</strong>";
html += "</div>";
html += "<div class='channel-stat'>";
html += "<span>Energy:</span><strong>" + String(channels[i].energy, 3) + " kWh</strong>";
html += "</div>";
html += "<div class='channel-stat'>";
html += "<span>Cost:</span><strong>$" + String(channels[i].cost, 2) + "</strong>";
html += "</div>";
// Power bar
int powerPercent = constrain((channels[i].power / 1000.0) * 100, 0, 100);
html += "<div class='progress-bar'><div class='progress-fill' style='width:" + String(powerPercent) + "%'></div></div>";
// Relay control
html += "<div style='margin-top:15px;text-align:center'>";
html += "<span>Status: " + String(channels[i].relayState ? "ON" : "OFF") + "</span><br>";
if (channels[i].relayState) {
html += "<button onclick=\"window.location='/relay" + String(i+1) + "/off'\" class='btn btn-off'>Turn OFF</button>";
} else {
html += "<button onclick=\"window.location='/relay" + String(i+1) + "/on'\" class='btn btn-on'>Turn ON</button>";
}
html += "</div>";
html += "</div>";
}
html += "</div>";
// Controls
html += "<div class='controls'>";
html += "<button onclick=\"window.location='/reset'\" class='btn btn-reset'>🔄 Reset Counters</button>";
html += "</div>";
html += "<div style='text-align:center;margin-top:30px;opacity:0.5;font-size:0.9em'>";
unsigned long uptime = (millis() - energySystem.startTime) / 1000;
html += "Uptime: " + String(uptime / 3600) + "h " + String((uptime % 3600) / 60) + "m | ";
html += "Rate: $" + String(energySystem.costPerKWh, 2) + "/kWh | ";
html += "IP: " + WiFi.localIP().toString();
html += "</div>";
html += "</div></body></html>";
server.send(200, "text/html", html);
}
void handleStatus() {
String json = "{";
json += "\"totalPower\":" + String(energySystem.totalPower, 2) + ",";
json += "\"totalEnergy\":" + String(energySystem.totalEnergy, 4) + ",";
json += "\"totalCost\":" + String(energySystem.totalCost, 2) + ",";
json += "\"averagePower\":" + String(energySystem.averagePower, 2) + ",";
json += "\"channels\":[";
for (int i = 0; i < 3; i++) {
if (i > 0) json += ",";
json += "{";
json += "\"name\":\"" + channels[i].name + "\",";
json += "\"voltage\":" + String(channels[i].voltage, 2) + ",";
json += "\"current\":" + String(channels[i].current, 3) + ",";
json += "\"power\":" + String(channels[i].power, 2) + ",";
json += "\"energy\":" + String(channels[i].energy, 4) + ",";
json += "\"cost\":" + String(channels[i].cost, 2) + ",";
json += "\"relay\":" + String(channels[i].relayState ? "true" : "false");
json += "}";
}
json += "]}";
server.send(200, "application/json", json);
}
void handleHistory() {
String json = "[";
for (int i = 0; i < MAX_HISTORY; i++) {
if (i > 0) json += ",";
json += String(powerHistory[i], 1);
}
json += "]";
server.send(200, "application/json", json);
}
void handleReset() {
for (int i = 0; i < 3; i++) {
channels[i].energy = 0;
channels[i].cost = 0;
channels[i].peakPower = 0;
channels[i].alertCount = 0;
}
energySystem.totalEnergy = 0;
energySystem.totalCost = 0;
energySystem.lastReset = millis();
Serial.println("✓ Energy counters reset");
server.sendHeader("Location", "/");
server.send(303);
}