Intelligent IoT Energy Meter with Electricity Theft Detection
ESP32 + n8n Automation + AI Agent + Telegram Voice Alerts + Gmail + Google Sheets + ThingSpeak + Web Dashboard
This project is an intelligent electricity monitoring and abnormal-consumption detection system that combines an ESP32 microcontroller, energy measurement sensors, cloud monitoring, workflow automation, and AI-assisted notifications.
The system continuously measures electrical parameters, estimates energy consumption, identifies suspicious patterns, and sends alerts through Telegram, email, and optional voice notifications. It can also maintain a historical record in Google Sheets, display live and historical measurements in ThingSpeak, and provide a custom IoT webpage.
The documentation below is structured as a complete engineering project guide, including the system architecture, circuit and wiring plan, flowcharts, software requirements, firmware, n8n workflows, AI agent logic, dashboard design, testing, and deployment.
Project objectives
Real-time energy monitoring
Measure voltage, current, active power, power factor, frequency where supported, and accumulated energy.
Theft and tampering detection
Flag possible bypassing, unexpected current, meter/load discrepancies, and sensor or enclosure tampering where appropriate sensors are installed.
Automated response
Use n8n to process telemetry, apply rules, invoke an AI agent for explanation, and coordinate Telegram and Gmail alerts.
Cloud dashboard and reporting
Store measurements, plot trends, review incidents, and generate reports using Google Sheets, ThingSpeak, and a web interface.
Important engineering distinction: an abnormal energy pattern can indicate a fault, measurement error, unauthorized load, or possible theft. An ESP32 prototype cannot prove electricity theft by itself. Reliable detection requires appropriate sensor placement, calibrated measurements, and comparison against a trusted reference or other tamper sensors.
This is an educational prototype design, not a certified utility billing meter. Do not connect exposed mains-voltage circuits to a breadboard or directly to ESP32 pins.
1. Complete system architecture
The project has five major layers:
-
Electrical measurement layer: sensors measure voltage, current, power, and energy.
-
Edge-computing layer: the ESP32 collects readings, checks local safety thresholds, and transmits data.
-
Cloud and automation layer: n8n receives readings and executes detection, logging, and notification workflows.
-
AI communication layer: an AI agent explains incidents in plain language and prepares a short voice-alert script.
-
User interface layer: Telegram, Gmail, Google Sheets, ThingSpeak, and an IoT webpage present the information.
Electrical load and energy meter
Voltage • Current • Power • Energy • Tamper sensors
ESP32 IoT controller
Read sensors • Validate values • Detect local anomalies • Wi-Fi
n8n automation server
Webhook • Rules engine • AI agent • Incident management
Telegram
Text and voice alerts
Gmail
Incident emails
Google Sheets
Measurement history
ThingSpeak
Charts and trends
IoT webpage / monitoring dashboard
Live readings • Energy totals • Incident history • Acknowledgements
The ESP32 can use Wi-Fi station mode to connect to your network. n8n's Webhook node can receive HTTP requests and trigger workflows, while its integrations can connect to Telegram and Google Sheets.
End-to-end data flow
flowchart TD
A[Electrical supply and load] --> B[Energy measurement module]
B --> C[ESP32 firmware]
C --> D{Sensor values valid?}
D -- No --> E[Record sensor fault]
D -- Yes --> F[Calculate anomaly indicators]
F --> G[HTTPS POST to n8n]
G --> H[Validate device and payload]
H --> I[Save telemetry]
I --> J{Anomaly detected?}
J -- No --> K[Update dashboard]
J -- Yes --> L[Create incident]
L --> M[AI explanation]
M --> N[Telegram alert]
M --> O[Gmail alert]
M --> P[Generate voice message]
P --> Q[Telegram voice/audio]
I --> R[Google Sheets]
I --> S[ThingSpeak]
Design rule: basic threshold detection should run locally or in deterministic automation. AI should explain and summarize an event, not decide whether an electrical installation is safe.
2. Project description and working principle
2.1 How the system works
When an electrical load is switched on, the energy measurement module measures the relevant electrical quantities. The ESP32 reads those values, assigns a device identifier and timestamp, checks for missing or out-of-range measurements, and sends a JSON payload to an n8n webhook.
The workflow then:
-
Validates and normalizes the incoming data.
-
Checks defined limits and tamper indicators.
-
Saves the reading to a data store.
-
Updates the dashboard and cloud charts.
-
Generates an incident if a rule is triggered.
-
Uses an AI model to create a human-readable explanation.
-
Sends a Telegram message and an email.
-
Optionally generates a speech file and delivers it as Telegram audio.
The monitoring loop repeats continuously. A separate scheduled workflow can generate daily energy summaries and reports.
2.2 What counts as possible electricity theft?
A good project must distinguish between a measured anomaly and confirmed theft.
|
Detection condition |
Possible interpretation |
|---|---|
|
Upstream current is high but downstream current is low |
Possible bypass, leakage, or sensor mismatch |
|
Meter energy differs persistently from an independent reference meter |
Possible metering discrepancy or unauthorized connection |
|
Enclosure switch opens unexpectedly |
Possible tampering |
|
Current rises beyond the configured safe or expected limit |
Overload or unexpected load |
|
Voltage/current suddenly becomes zero |
Power outage, disconnected sensor, or supply interruption |
|
Measurements stop arriving |
Wi-Fi, power, firmware, or sensor failure |
A practical theft-detection setup should ideally have a trusted upstream reference measurement and a downstream measurement. A single energy meter can detect unusual behavior, but it cannot reliably identify a bypass around itself.
2.3 Detection logic
For an installation with upstream and downstream current sensors:
Flag a possible discrepancy when:
for a configurable duration, provided that both sensors are healthy and the readings are time-aligned.
For an independent energy comparison:
A persistent difference greater than a calibrated tolerance can create an incident for investigation. Account for sensor accuracy, measurement location, auxiliary loads, and expected losses.
For energy consumption, when power is measured in watts and elapsed time in seconds:
For accurate active power and power factor, use an appropriate calibrated measurement device rather than deriving billing-grade values from voltage and current alone. See the OpenEnergyMonitor measurement and calibration documentation.
3. Hardware requirements
The recommended first prototype uses a digital energy meter module with a UART interface. That keeps the ESP32 firmware manageable and avoids implementing waveform sampling and power calculations from scratch.
|
Component |
Purpose |
|---|---|
|
ESP32 DevKit |
Main microcontroller and Wi-Fi connection |
|
PZEM-004T v3 or equivalent suitable energy meter |
Voltage, current, active power, energy, and supported additional parameters |
|
Current transformer supplied for the selected meter |
Current measurement where required by the module |
|
Isolated, regulated power supply |
Powers the low-voltage electronics |
|
Enclosure tamper switch |
Detects opening of the protected enclosure |
|
Optional second reference meter/current sensor |
Improves discrepancy detection |
|
Breadboard and jumper wires |
Low-voltage prototyping only |
|
Enclosed terminals, fuse and suitable protection |
Properly engineered installation |
|
Computer with Arduino IDE |
Firmware development |
|
n8n instance |
Workflow automation |
|
Telegram bot, Google account and ThingSpeak channel |
Notifications and cloud storage |
Electrical safety: PZEM-004T variants and wiring arrangements differ. Follow the exact module's documentation. Mains connections, current transformers, isolation, fusing, and enclosure construction must be designed and checked by a qualified person. Do not assume that a low-voltage UART interface makes every part of the meter module safe to touch.
Recommended measurement options
-
Beginner: PZEM-004T with ESP32 UART, using its documented isolated interface and suitable installation.
-
Advanced: isolated voltage sensing and CT measurement with a validated energy-monitoring library.
-
Research-grade comparison: two calibrated measurement channels or an independent reference meter.
The example firmware below targets the first option.
4. Schematic and connection diagram
The following is a low-voltage signal schematic, not a mains wiring diagram.
LOW-VOLTAGE SIGNAL SIDE
+-------------------------+
| ESP32 DevKit |
| |
| GPIO16 (RX2) <----------+---- PZEM TX
| GPIO17 (TX2) ---------->+---- PZEM RX
| GND --------------------+---- PZEM interface GND*
| GPIO27 <--------------------- Tamper switch
| 3V3 ---- optional logic supply only
+-------------------------+
PZEM measurement terminals
|
+---- Installed only according
| to the exact meter manual
|
Properly protected AC circuit
* Connect signal grounds only if the specific module's documented interface requires and permits it. Do not bridge an isolation barrier or assume the module uses 3.3 V UART logic without verifying the specification.
Connection table
|
ESP32 pin |
Connected device |
Purpose |
|---|---|---|
|
GPIO16 (RX2) |
Meter TX |
Receives meter readings |
|
GPIO17 (TX2) |
Meter RX |
Sends meter commands |
|
GPIO27 |
Tamper switch |
Enclosure-open input |
|
GND |
Compatible low-voltage signal ground |
Reference, only as documented |
For the tamper input, this design uses INPUT_PULLUP and a normally closed switch connected to ground. A closed switch reads LOW; opening the circuit reads HIGH. A broken wire may also appear as tampering, which is a useful fail-safe behavior but can create false alarms.
Before connecting the UART, verify the voltage levels, terminal labels, interface isolation, and supply requirements for the exact meter model.
5. Software installation
5.1 Install the ESP32 development environment
-
Install the Arduino IDE from the official Arduino website.
-
Install the ESP32 board package using the Espressif Arduino documentation.
-
Select the correct ESP32 board and serial port.
-
Install the
PZEM004Tv30library using the Library Manager, or use the maintained library version compatible with your hardware. -
Compile a basic serial example before connecting the cloud services.
-
Confirm that the ESP32 reads the meter correctly over UART.
Official references:
5.2 Create the cloud accounts and credentials
-
n8n: run n8n Cloud or a properly secured self-hosted instance.
-
Telegram: create a bot through
@BotFather, obtain its token, and determine the destination chat ID. -
Gmail: configure the Gmail integration or an authorized email-sending method in n8n.
-
Google Sheets: create a spreadsheet and connect it using n8n credentials.
-
ThingSpeak: create a channel and enable the fields required for your measurements.
-
AI model: configure a supported language-model credential in n8n.
-
Voice generation: select a text-to-speech service or a local TTS engine.
Keep API keys, Wi-Fi passwords, bot tokens, and email credentials out of public source repositories.
6. ESP32 firmware — complete starter implementation
This firmware reads a PZEM-compatible energy meter, monitors an enclosure switch, detects simple abnormal conditions, uploads measurements to n8n, and updates ThingSpeak.
The example is intended for a common ESP32 DevKit with UART2 available on GPIO16 and GPIO17. Pin assignments and the exact library API should be checked against your board and meter.
6.1 Firmware functions
-
Connects to Wi-Fi.
-
Reads voltage, current, power, energy, frequency and power factor when supported by the meter.
-
Detects a high-current condition.
-
Detects an open tamper switch.
-
Sends JSON to an n8n webhook.
-
Sends measurement fields to ThingSpeak.
-
Continues local measurement if cloud connectivity fails.
The firmware does not claim to prove electricity theft. It supplies telemetry and preliminary anomaly flags to the automation workflow.
6.2 Configure the Arduino sketch
Create a sketch called SmartEnergyMeter.ino and install the PZEM004Tv30 library.
The following code is a practical starter. Replace the placeholders before uploading.
#include <WiFi.h>
#include <HTTPClient.h>
#include <PZEM004Tv30.h>
#include <math.h>
// -------- USER CONFIGURATION --------
const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
// Use your n8n production webhook URL.
const char* N8N_WEBHOOK =
"https://YOUR_N8N_HOST/webhook/energy-meter";
// ThingSpeak channel WRITE API key.
const char* THINGSPEAK_WRITE_KEY = "YOUR_WRITE_API_KEY";
// Must match the device identity configured in n8n.
const char* DEVICE_ID = "energy-meter-01";
// UART2: ESP32 RX receives meter TX; ESP32 TX sends meter RX.
HardwareSerial meterSerial(2);
PZEM004Tv30 pzem(meterSerial, 16, 17);
const int TAMPER_PIN = 27;
const float MAX_CURRENT_A = 15.0; // Example only: set appropriately
const unsigned long SAMPLE_MS = 10000;
// -------- STATE --------
unsigned long lastSample = 0;
unsigned long lastN8nUpload = 0;
unsigned long lastThingSpeakUpload = 0;
bool previousTamper = false;
float previousEnergy = NAN;
// Escape text before inserting it into a JSON string.
String jsonEscape(const String& s) {
String out;
for (size_t i = 0; i < s.length(); i++) {
char c = s[i];
if (c == '"' || c == '\\') {
out += '\\';
out += c;
} else if (c == '\n') {
out += "\\n";
} else if (c == '\r') {
out += "\\r";
} else if ((uint8_t)c >= 0x20) {
out += c;
}
}
return out;
}
void connectWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting to Wi-Fi");
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED &&
millis() - start < 15000) {
delay(300);
Serial.print(".");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Connected. IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("Wi-Fi unavailable; local monitoring continues.");
}
}
bool validReading(float x) {
return !isnan(x) && isfinite(x) && x >= 0.0f;
}
bool postJson(const String& payload) {
if (WiFi.status() != WL_CONNECTED) return false;
WiFiClientSecure client;
// DEMONSTRATION ONLY:
// In production, configure the appropriate CA certificate
// using client.setCACert(rootCACertificate).
// Do not deploy setInsecure() in a production installation.
client.setInsecure();
HTTPClient http;
if (!http.begin(client, N8N_WEBHOOK)) return false;
http.setTimeout(5000);
http.addHeader("Content-Type", "application/json");
// Add a device authentication header in production.
// Never put a long-lived secret in public source code.
int code = http.POST(payload);
String response = http.getString();
Serial.printf("n8n HTTP status: %d\n", code);
if (code > 0) Serial.println(response);
http.end();
return code >= 200 && code < 300;
}
bool sendThingSpeak(float voltage, float current,
float power, float energy) {
if (WiFi.status() != WL_CONNECTED) return false;
if (!validReading(voltage) || !validReading(current) ||
!validReading(power) || !validReading(energy)) {
return false;
}
WiFiClientSecure client;
// Demonstration only; use certificate verification in production.
client.setInsecure();
HTTPClient http;
String url = "https://api.thingspeak.com/update?api_key=";
url += THINGSPEAK_WRITE_KEY;
url += "&field1=" + String(voltage, 2);
url += "&field2=" + String(current, 3);
url += "&field3=" + String(power, 2);
url += "&field4=" + String(energy, 4);
if (!http.begin(client, url)) return false;
http.setTimeout(5000);
int code = http.GET();
String response = http.getString();
http.end();
Serial.printf("ThingSpeak HTTP status: %d\n", code);
// ThingSpeak commonly returns a nonzero entry number on success.
return code == 200 && response.toInt() > 0;
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(TAMPER_PIN, INPUT_PULLUP);
// UART2 uses RX=GPIO16 and TX=GPIO17.
meterSerial.begin(9600, SERIAL_8N1, 16, 17);
connectWiFi();
Serial.println("Smart Energy Meter started.");
}
void loop() {
if (millis() - lastSample < SAMPLE_MS) {
delay(20);
return;
}
lastSample = millis();
if (WiFi.status() != WL_CONNECTED) {
connectWiFi();
}
float voltage = pzem.voltage();
float current = pzem.current();
float power = pzem.power();
float energy = pzem.energy();
float frequency = pzem.frequency();
float pf = pzem.pf();
// The switch is closed to GND in the normal state.
bool tamper = (digitalRead(TAMPER_PIN) == HIGH);
bool sensorFault =
!validReading(voltage) ||
!validReading(current) ||
!validReading(power) ||
!validReading(energy);
bool highCurrent =
!sensorFault && current > MAX_CURRENT_A;
bool energyReset =
!isnan(previousEnergy) &&
!sensorFault &&
energy + 0.001f < previousEnergy;
if (!sensorFault) previousEnergy = energy;
String event = "NORMAL";
if (sensorFault) event = "SENSOR_FAULT";
else if (tamper) event = "TAMPER";
else if (highCurrent) event = "HIGH_CURRENT";
else if (energyReset) event = "ENERGY_COUNTER_RESET";
String payload = "{";
payload += "\"device_id\":\"" + jsonEscape(DEVICE_ID) + "\",";
payload += "\"voltage_v\":" +
(validReading(voltage) ? String(voltage, 2) : "null") + ",";
payload += "\"current_a\":" +
(validReading(current) ? String(current, 3) : "null") + ",";
payload += "\"power_w\":" +
(validReading(power) ? String(power, 2) : "null") + ",";
payload += "\"energy_kwh\":" +
(validReading(energy) ? String(energy, 4) : "null") + ",";
payload += "\"frequency_hz\":" +
(validReading(frequency) ? String(frequency, 2) : "null") + ",";
payload += "\"power_factor\":" +
(validReading(pf) ? String(pf, 3) : "null") + ",";
payload += "\"tamper\":" + String(tamper ? "true" : "false") + ",";
payload += "\"sensor_fault\":" +
String(sensorFault ? "true" : "false") + ",";
payload += "\"high_current\":" +
String(highCurrent ? "true" : "false") + ",";
payload += "\"energy_counter_reset\":" +
String(energyReset ? "true" : "false") + ",";
payload += "\"event\":\"" + event + "\",";
payload += "\"uptime_ms\":" + String(millis());
payload += "}";
Serial.println(payload);
// Upload to n8n at the sample interval.
if (millis() - lastN8nUpload >= SAMPLE_MS) {
if (postJson(payload)) lastN8nUpload = millis();
}
// Upload to ThingSpeak less frequently.
// Check the current account/channel write-rate limits.
if (!sensorFault &&
millis() - lastThingSpeakUpload >= 20000) {
if (sendThingSpeak(voltage, current, power, energy)) {
lastThingSpeakUpload = millis();
}
}
if (tamper && !previousTamper) {
Serial.println("ALERT: enclosure tamper input activated.");
}
previousTamper = tamper;
}
6.3 Firmware configuration notes
Before deploying the sketch:
-
Replace the Wi-Fi credentials.
-
Set
N8N_WEBHOOKto your real production webhook URL. -
Enter your ThingSpeak write key.
-
Configure the current threshold based on the installed circuit and sensor rating. The example's 15 A is not a universal safe limit.
-
Verify that your library constructor matches the installed version.
-
Configure certificate validation instead of
setInsecure()before deployment. -
Add device authentication, request validation, and a retry queue for production.
-
Test sensor failures, Wi-Fi outages, and meter resets.
The code's event field is a simple first-match status. If multiple conditions occur simultaneously, the n8n workflow should examine the individual Boolean flags rather than relying only on that single field.
Important: the ESP32's built-in ADC is not used to measure mains voltage directly in this design. The measurement module handles the electrical measurement.
7. Create the ThingSpeak cloud dashboard
ThingSpeak stores measurements in channel fields and supports HTTP-based data uploads.
Open ThingSpeak and create a new channel named Smart Energy Meter.
Configure these fields:
|
Channel field |
Name |
Units |
|---|---|---|
|
Field 1 |
Voltage |
V |
|
Field 2 |
Current |
A |
|
Field 3 |
Active Power |
W |
|
Field 4 |
Energy |
kWh |
|
Field 5 |
Frequency |
Hz |
|
Field 6 |
Power Factor |
Unitless |
|
Field 7 |
Tamper Status |
0 or 1 |
|
Field 8 |
Alert Code |
Numeric code |
Enable only the fields you need. Add charts for voltage, current, active power, and energy. Use the channel's private read configuration for sensitive installations.
ThingSpeak setup steps
-
Create a channel and enable the fields.
-
Copy the channel's Write API Key.
-
Copy the channel ID and use it for the dashboard.
-
Enter the write key in the firmware.
-
Upload a few test readings.
-
Confirm that the channel updates.
-
Add chart widgets and select appropriate time ranges.
The starter firmware sends fields 1–4. To populate fields 5–8, extend the ThingSpeak request or send data through n8n.
8. Build the n8n automation workflow
n8n provides a Webhook node for receiving telemetry, along with application nodes for integrating communication and storage systems.
Workflow 1: telemetry ingestion and alerting
Node sequence
-
Webhook —
energy-meterMethod:
POST. Configure a production URL and an appropriate response mode. -
Code or Edit Fields — validate payload
Check
device_id, numeric values, timestamps or sequence numbers, and required fields. Reject malformed or unauthorized requests. -
Switch / IF — evaluate deterministic rules
Branch on
tamper,sensor_fault,high_current, and additional reference-meter discrepancy fields if implemented. -
Google Sheets — append row
Save the timestamp, device identifier, electrical readings, event, and alert status.
-
ThingSpeak — HTTP Request (optional)
Write additional channel fields not sent directly by the ESP32.
-
AI Agent or language-model node
Generate a concise explanation and recommended inspection steps from validated measurements.
-
Telegram and Gmail
Send notifications for incidents; use a separate branch for voice generation if enabled.
Example n8n rule logic
Use a Code node after validating the incoming webhook payload. This code creates a rule-based alert without requiring an AI model.
const d = $json.body ?? $json;
const required = ["device_id", "voltage_v",
"current_a", "power_w", "energy_kwh"];
for (const key of required) {
if (!(key in d)) {
throw new Error(`Missing field: ${key}`);
}
}
const numericFields = [
"voltage_v", "current_a", "power_w", "energy_kwh"
];
for (const key of numericFields) {
if (d[key] !== null &&
(typeof d[key] !== "number" ||
!Number.isFinite(d[key]) ||
d[key] < 0)) {
throw new Error(`Invalid measurement: ${key}`);
}
}
const incidents = [];
if (d.sensor_fault === true) {
incidents.push("SENSOR_FAULT");
}
if (d.tamper === true) {
incidents.push("TAMPER");
}
if (d.high_current === true) {
incidents.push("HIGH_CURRENT");
}
if (d.energy_counter_reset === true) {
incidents.push("ENERGY_COUNTER_RESET");
}
return [{
json: {
...d,
incidents,
alert_required: incidents.length > 0,
received_at: new Date().toISOString()
}
}];
This is a starter validation node, not a complete security boundary. Production workflows should authenticate devices, rate-limit requests, validate ranges against the actual installation, prevent replayed requests, and handle missing or stale telemetry.
Workflow 2: incident notifications
flowchart TD
A[Incident from validated telemetry] --> B[Deduplicate event]
B --> C[Create incident record]
C --> D[AI summarizes measurements]
D --> E{Severity policy}
E -->|Informational| F[Log only]
E -->|Warning| G[Telegram text + Gmail]
E -->|Critical| H[Telegram + Gmail + voice]
H --> I[Record delivery status]
G --> I
F --> I
Configure notification severity separately from the AI explanation. For example, an enclosure-open event may require immediate notification, while a small discrepancy may require repeated observations before an alert.
Workflow 3: scheduled energy report
Use a Schedule Trigger to run daily:
-
Read the day's data from your chosen storage.
-
Calculate energy consumed, peak measured power, and alert count.
-
Compare against the previous day.
-
Generate a brief AI summary.
-
Append a report to Google Sheets.
-
Send the report by email or Telegram.
Do not ask the AI to invent missing values. If there is insufficient data, the report should explicitly identify the gap.
9. AI agent configuration
The AI agent adds value by explaining an event, producing a readable notification, and suggesting investigation steps. It should not independently disconnect electricity or declare someone guilty of theft.
Suggested system instructions
Paste the following into the agent's system instructions or equivalent model-instruction field.
You are an assistant for an IoT electrical energy monitoring system.
Your task is to explain validated measurements and detected incidents.
Use only the supplied telemetry and established detection rules.
Rules:
1. Never claim that electricity theft is proven from an anomaly alone.
2. Distinguish a sensor fault, overload, tamper event,
measurement discrepancy, and possible unauthorized consumption.
3. Do not invent missing readings, timestamps, or device details.
4. If sensor_fault is true, report that the readings may be unreliable.
5. If a tamper event is present, describe it as a tamper indication.
6. Do not instruct users to touch exposed electrical equipment.
7. Recommend qualified inspection when electrical safety is involved.
8. Return concise JSON with:
title, severity, explanation, evidence, recommended_action,
and voice_script.
9. The severity must be consistent with the deterministic
incident rules supplied by the workflow.
10. Do not operate relays, breakers, or other safety-critical
controls without a separately engineered and authorized system.
n8n supports AI-agent functionality and model-connected tools; the available setup depends on the version and hosting environment.
Example agent input
{
"device_id": "energy-meter-01",
"voltage_v": 231.8,
"current_a": 16.2,
"power_w": 3500.0,
"energy_kwh": 42.61,
"tamper": false,
"high_current": true,
"sensor_fault": false,
"incidents": ["HIGH_CURRENT"]
}
Example expected output
{
"title": "High-current condition",
"severity": "warning",
"explanation": "Measured current exceeds the configured threshold.",
"evidence": {
"current_a": 16.2,
"configured_threshold_a": 15.0
},
"recommended_action": "Review the load and have the circuit inspected if the condition persists.",
"voice_script": "Warning. High current has been detected by the energy monitoring system. Please review the reported load and arrange an inspection if required."
}
The threshold in this example is illustrative, not a statement about the safe rating of any actual circuit.
10. Telegram text and voice alerts
The Telegram integration in n8n supports messaging and audio-related operations.
10.1 Create the Telegram bot
-
Open Telegram and search for
@BotFather. -
Start a conversation and use
/newbot. -
Give your bot a display name and a unique username.
-
Save the generated bot token in a secure location.
-
Open your new bot and press Start.
-
Obtain the destination chat ID using an authorized bot update or n8n's Telegram integration.
-
Add Telegram credentials in n8n and test sending a message.
Never share the bot token publicly; anyone with access to it may be able to control your bot.
10.2 Example text alert
Smart Energy Meter
Example notification
⚡ High-current condition detected
-
Device:
energy-meter-01 -
Voltage: 231.8 V
-
Current: 16.2 A
-
Active power: 3,500 W
-
Energy: 42.61 kWh
-
Status: Current exceeds configured threshold
Recommended action: Review the load and arrange a qualified inspection if the condition persists.
Example values only. A warning is not proof of electricity theft.
In n8n, configure the Telegram node to send a message using a template such as:
⚡ SMART ENERGY METER ALERT
Device: {{$json.device_id}}
Event: {{$json.title}}
Severity: {{$json.severity}}
Voltage: {{$json.voltage_v}} V
Current: {{$json.current_a}} A
Power: {{$json.power_w}} W
Energy: {{$json.energy_kwh}} kWh
Explanation: {{$json.explanation}}
Action: {{$json.recommended_action}}
The expressions above assume that the AI output and telemetry have already been merged into the same item. Adjust the field references to match your workflow.
10.3 Generate Telegram voice notifications
A voice alert requires a text-to-speech service. Telegram itself does not automatically convert arbitrary text into speech.
Use this workflow:
flowchart TD
A[Incident JSON] --> B[AI creates voice_script]
B --> C[Text-to-speech API]
C --> D[Receive MP3 or OGG file]
D --> E[Upload file in Telegram node]
E --> F[User hears voice alert]
C --> G[Store audio URL or incident reference]
Implementation steps:
-
Add a text-to-speech provider node or an HTTP Request node.
-
Send the
voice_scriptas the input text. -
Request audio in a format supported by the provider.
-
Receive the generated audio as binary data or a file URL.
-
Pass the binary data to the Telegram node's audio operation.
-
Use a voice-message operation if you need Telegram's voice-note presentation and the audio is encoded in a compatible format.
-
Log the incident ID and delivery result.
For the simplest version, send an MP3 as Telegram audio. For an actual voice note, verify the provider's supported output format and Telegram upload requirements. A hosted audio URL should not be exposed publicly unless access is intentionally authorized.
11. Gmail alerts and email reports
Use n8n's Gmail integration with authorized Google credentials. A Gmail alert can include the complete event, a dashboard link, and an explanation of what should be checked.
Example email notification
Subject: Smart Energy Meter — High-current warning
Dear Monitoring Team,
The IoT energy meter has reported a current value exceeding its configured threshold.
-
Device:
energy-meter-01 -
Measured current: 16.2 A
-
Configured threshold: 15.0 A
-
Voltage: 231.8 V
-
Active power: 3,500 W
The reading indicates a high-current condition. It does not establish that electricity theft has occurred.
Please review the electrical load and arrange a qualified inspection if the condition persists.
Regards,
Smart Energy Monitoring System
In the n8n Gmail node, set the recipient, subject, and body. Use validated values and include the incident ID so that recipients can correlate the email with Telegram messages and spreadsheet records.
12. Google Sheets database
Google Sheets is useful for a prototype because it makes the history easy to inspect and export.
Create a spreadsheet named Smart_Energy_Monitoring.
Sheet 1: Telemetry
Use the following column headers:
|
Column |
Header |
|---|---|
|
A |
timestamp |
|
B |
device_id |
|
C |
voltage_v |
|
D |
current_a |
|
E |
power_w |
|
F |
energy_kwh |
|
G |
frequency_hz |
|
H |
power_factor |
|
I |
tamper |
|
J |
sensor_fault |
|
K |
high_current |
|
L |
event |
|
M |
uptime_ms |
Sheet 2: Incidents
|
Column |
Header |
|---|---|
|
A |
incident_id |
|
B |
timestamp |
|
C |
device_id |
|
D |
incident_type |
|
E |
severity |
|
F |
evidence |
|
G |
AI explanation |
|
H |
recommended action |
|
I |
Telegram status |
|
J |
Gmail status |
|
K |
voice status |
|
L |
acknowledged_by |
|
M |
resolved_at |
n8n configuration
-
Add a Google Sheets node.
-
Select the spreadsheet and the relevant worksheet.
-
Choose Append Row for telemetry records.
-
Map each JSON field to its corresponding column.
-
Use a separate incident branch to append incident records.
-
Record notification delivery status after each notification attempt.
Google Sheets is suitable for demonstrations and small installations. For high-volume or mission-critical monitoring, consider a database such as PostgreSQL with access control, retention policies, backups, and indexed timestamps.
13. IoT webpage and AI agent dashboard
A custom webpage should provide a single place to view current measurements, recent trends, incident status, and system health.
13.1 Recommended webpage layout
Smart Energy Monitor
Prototype dashboard preview
Voltage
231.8 V
Current
16.2 A
Active power
3.50 kW
Energy consumed
42.61 kWh
Current trend — sample data
Illustrative values for the dashboard mock-up, not actual device telemetry.
| Current | |
|---|---|
| 10:00 | 4.2 A |
| 10:05 | 4.8 A |
| 10:10 | 5.1 A |
| 10:15 | 7.4 A |
| 10:20 | 16.2 A |
Latest incident
High-current warning
Measured value exceeds the configured threshold.
This is a visual mock-up using illustrative readings. It is not connected to your ESP32 or cloud accounts.
13.2 Dashboard features to implement
-
Live measurement cards.
-
Current and power trend charts.
-
Daily and monthly energy totals.
-
Event history with severity and timestamps.
-
Online/offline status based on the last valid telemetry time.
-
A tamper-event indicator.
-
AI-generated incident explanations.
-
Acknowledge and resolve controls.
-
CSV export and daily email reports.
13.3 Webpage architecture
For a simple implementation, use HTML, CSS, and JavaScript with a backend endpoint in n8n. The browser should call a read-only dashboard endpoint rather than exposing device write keys or n8n credentials.
flowchart LR
A[ESP32] --> B[n8n ingestion webhook]
B --> C[Database]
B --> D[ThingSpeak]
C --> E[n8n dashboard API]
E --> F[HTML CSS JavaScript]
D --> G[ThingSpeak charts]
F --> H[User sees readings and incidents]
Recommended implementation: store telemetry and incidents in a database, expose a secured read-only endpoint through n8n, and have the webpage refresh the latest measurements periodically. Use authentication for any endpoint that exposes private device data.
If you only need a quick prototype, you can embed a private or appropriately shared ThingSpeak visualization in the webpage. A custom dashboard provides more control over incident tables, AI summaries, and acknowledgement workflows.
14. Theft-detection logic: improved engineering version
The firmware's initial threshold checks are useful for testing, but a more convincing project needs several complementary detection rules.
Rule A: enclosure tampering
Trigger an event when the tamper switch opens.
-
Debounce the input.
-
Require a configurable confirmation period.
-
Send a notification.
-
Log the state change.
-
Report a broken sensor wire as a possible tamper or sensor fault.
Rule B: overcurrent or unexpected consumption
Trigger an alert if the measured current or power exceeds an installation-specific threshold for a specified duration.
A single unusually high reading should not necessarily be treated as an incident. Add filtering, hysteresis, and a confirmation interval to reduce false positives.
Rule C: reference-meter discrepancy
When both upstream and downstream measurements are available, compare them after correcting for sensor calibration and expected losses.
flowchart TD
A[Read upstream and downstream sensors] --> B[Check both sensors healthy]
B -->|No| C[Raise measurement fault]
B -->|Yes| D[Time-align and calibrate readings]
D --> E[Calculate discrepancy]
E --> F{Above tolerance?}
F -->|No| G[Continue monitoring]
F -->|Yes| H[Start persistence timer]
H --> I{Persists long enough?}
I -->|No| G
I -->|Yes| J[Create possible bypass/discrepancy incident]
J --> K[AI explains evidence]
K --> L[Notify and request inspection]
Rule D: energy-counter discrepancy
Compare accumulated energy from the installed meter against an independent trusted reference. Account for differences in meter location, sampling interval, clock synchronization, and resets before generating a discrepancy incident.
Rule E: device offline
If n8n has not received valid telemetry for a defined interval, create an offline event. Do not treat missing data as zero energy consumption.
15. Test plan and expected results
Test the system first with low-voltage simulations, known sensor inputs, or a safe test setup before connecting it to an electrical installation.
|
Test |
Procedure |
Expected result |
|---|---|---|
|
T1 — Wi-Fi |
Start ESP32 with valid credentials |
Connects and prints IP |
|
T2 — Meter reading |
Apply a known safe test load through the correctly installed meter |
Readings change plausibly |
|
T3 — Sensor fault |
Disconnect the supported meter interface in a controlled test |
Sensor-fault status reported |
|
T4 — High current |
Simulate a value above the configured threshold |
Warning event generated |
|
T5 — Tamper input |
Open the low-voltage switch |
Tamper event reported |
|
T6 — n8n webhook |
Send a valid test JSON request |
Workflow executes successfully |
|
T7 — Invalid payload |
Send a malformed or missing-field payload |
Rejected or quarantined |
|
T8 — Telegram |
Trigger a test incident |
Text notification arrives |
|
T9 — Gmail |
Trigger a test incident |
Email is delivered |
|
T10 — Voice |
Run the TTS branch |
Audio is generated and delivered |
|
T11 — Sheets |
Send several test readings |
Rows appear in the expected columns |
|
T12 — ThingSpeak |
Send a valid channel update |
Charts show new readings |
|
T13 — Network failure |
Disconnect Wi-Fi temporarily |
Local loop continues; reconnection is attempted |
|
T14 — Offline alert |
Stop telemetry for the configured interval |
Offline incident is created |
|
T15 — False-positive check |
Apply ordinary load changes |
No unsupported theft accusation is generated |
Acceptance criteria
-
Valid readings are recorded consistently.
-
Invalid or stale readings are identified.
-
Alerts include the device ID, event type, timestamp, and relevant evidence.
-
Repeated alerts are deduplicated or rate-limited.
-
Notification delivery failures are logged.
-
Recovery after network interruption is verified.
-
The AI never presents a possible discrepancy as conclusive proof of theft.
-
No safety-critical action depends on an AI-generated conclusion.
16. Installation and deployment procedure
Follow this sequence to avoid debugging the hardware, cloud services, and AI integration simultaneously.
-
Phase 1 — Hardware setup
Assemble the ESP32 and low-voltage interface. Have the mains measurement installation checked by a qualified electrician.
-
Phase 2 — Local firmware
Upload the firmware and confirm that readings are stable in the Arduino Serial Monitor.
-
Phase 3 — ThingSpeak
Create the channel and verify that voltage, current, power, and energy are being recorded.
-
Phase 4 — n8n webhook
Create the webhook, send a test JSON payload, validate it, and confirm successful execution.
-
Phase 5 — Google Sheets
Connect Google credentials and confirm that telemetry rows and incident records are appended correctly.
-
Phase 6 — Notifications
Configure Telegram and Gmail, then test each notification independently.
-
Phase 7 — AI and voice
Add the AI explanation step, connect a text-to-speech provider, and test the complete voice-delivery branch.
-
Phase 8 — Web dashboard
Build the webpage, connect its secured read-only endpoint, and verify its data against the stored measurements.
-
Phase 9 — Reliability and security
Test network outages, duplicate events, sensor faults, credentials, backups, and recovery procedures.
17. Security and reliability requirements
For a demonstration, the starter firmware is sufficient to establish the basic data flow. For a deployed system, implement the following controls:
-
Use HTTPS with certificate verification.
-
Authenticate devices using individual credentials or signed requests.
-
Rotate credentials and never commit them to public repositories.
-
Store API keys and AI-provider credentials in n8n's credential manager.
-
Restrict access to private ThingSpeak channels and spreadsheets.
-
Validate every incoming measurement and reject unexpected data.
-
Add a local queue for temporary network outages.
-
Deduplicate incidents and rate-limit repeated alerts.
-
Use watchdog and sensor-health checks.
-
Keep an audit trail for incident creation, acknowledgement, and resolution.
-
Use deterministic, independently engineered protection for electrical faults. Cloud notification is not a substitute for a fuse, circuit breaker, or suitable protective device.
18. Common problems and troubleshooting
|
Problem |
Likely cause |
What to check |
|---|---|---|
|
ESP32 cannot read meter |
UART pins or library mismatch |
Confirm baud rate, RX/TX direction and library version |
|
Readings show invalid values |
Meter wiring, communication failure or unsupported measurement |
Check the meter's documented wiring and error behavior |
|
Webhook receives no data |
Incorrect URL, TLS failure or Wi-Fi issue |
Check serial output and n8n execution logs |
|
Telegram messages fail |
Incorrect bot token or chat ID |
Test the credentials and destination chat |
|
Gmail fails |
Authorization or account permissions |
Reconnect the authorized Google account |
|
Google Sheets rows are empty |
Incorrect field mappings |
Inspect the input JSON and column names |
|
ThingSpeak charts are blank |
Incorrect key, field configuration or upload interval |
Check the HTTP response and channel feed |
|
Too many alerts |
No debounce, persistence, or deduplication |
Add thresholds, confirmation windows and incident state |
|
Voice alert does not play |
Unsupported audio format or incorrect binary mapping |
Test the TTS file independently before Telegram upload |
|
Dashboard is stale |
Cached data, failed API or device offline |
Check the latest successful telemetry timestamp |
19. Future enhancements
Once the basic system works, you can extend it with:
-
Three-phase energy monitoring.
-
Separate measurements for multiple circuits.
-
A second reference sensor for improved bypass detection.
-
Machine-learning anomaly detection trained on verified historical readings.
-
MQTT instead of HTTP for larger fleets of devices.
-
A PostgreSQL database for long-term history.
-
A progressive web app with login and mobile-friendly charts.
-
Voice queries such as “What was today's energy consumption?”
-
Daily, weekly, and monthly energy reports.
-
Authorized incident acknowledgement and maintenance tracking.
-
Predictive analysis for unusual load patterns.
A true agentic implementation can let the AI retrieve historical readings, summarize incident trends, prepare reports, and recommend follow-up checks. Keep its tools permission-limited and require explicit authorization for any consequential action.
20. Official documentation and references
These resources will help you implement the individual components using their current documentation.
ESP32 firmware and Wi-Fi
Official Arduino core setup, networking, and hardware references.
n8n automation and AI
Webhooks, workflow nodes, integrations, and agent configuration.
Energy measurement principles
Current sensing, voltage measurement, calibration, and sources of error.
21. Final project deliverables
A complete submission or final-year project report should contain these artifacts:
-
Project abstract and problem statement.
-
Objectives, scope, and requirements.
-
Hardware component list and specifications.
-
Electrical safety notes and verified circuit schematic.
-
ESP32 firmware source code.
-
n8n workflow exports and configuration guide.
-
AI agent instructions and output schema.
-
Telegram, Gmail, Google Sheets, and ThingSpeak setup documentation.
-
Webpage source code and dashboard screenshots.
-
Flowcharts, sequence diagrams, and system architecture.
-
Test cases, results, and troubleshooting.
-
Security analysis, limitations, conclusion, and future scope.
One final note about the supplied YouTube video: this is a complete reference architecture and starter implementation based on the features you described, not a verified reproduction of every component or wiring detail shown in that particular video. The exact sensor model, pinout, calibration, and original workflow configuration must be checked against the actual hardware and video.
The most important next step is to confirm your intended hardware, because that determines the final schematic and measurement code.
Below is a complete, build-ready project document for an Intelligent IoT Energy Meter with Theft Detection using ESP32 + PZEM-004T + n8n + Telegram/Gmail + Google Sheets + ThingSpeak + AI voice alerts. It includes architecture, wiring, firmware, web dashboard, n8n automation, and theft-detection logic.
Safety: This project measures mains AC power. Use a certified PZEM-004T module, proper enclosure, fusing, isolation, and qualified electrician supervision. Never touch live terminals while powered.
Project overview
The ESP32 reads voltage, current, power, energy, frequency, and power factor from a PZEM-004T v3.0 energy-meter module over UART/Modbus. It publishes telemetry to ThingSpeak, stores events in Google Sheets through n8n, displays a local IoT dashboard, and sends Telegram text/voice, Gmail, and AI-generated voice alerts when theft or abnormal consumption is detected. ThingSpeak supports REST writes from ESP32 devices and provides fields, charts, and channel data through its API.[mathworks][github]
Core features
- Real-time AC voltage, current, active power, apparent power, energy, frequency, and power factor.
- Local ESP32 web dashboard with auto-refreshing readings.
- Theft detection using current-without-voltage, sudden load change, reverse-energy anomaly, tamper, and offline/communication-failure checks.
- Cloud dashboard and historical graphs using ThingSpeak.
- n8n automation for Google Sheets logging, Gmail email alerts, Telegram text alerts, and AI voice alerts.
- Telegram bot command support:
/status,/energy,/theft,/help. - Optional relay control for remote load disconnect after confirmed theft.
System architecture
flowchart LR
A[AC Mains Load] --> B[PZEM-004T Energy Meter]
B -- UART / Modbus RTU --> C[ESP32]
C --> D[Local IoT Web Dashboard]
C -- HTTPS JSON --> E[n8n Webhook]
C -- HTTP GET /update --> F[ThingSpeak Cloud]
E --> G{Theft / Anomaly Rules}
G --> H[Google Sheets]
G --> I[Gmail Alert]
G --> J[Telegram Text Alert]
G --> K[OpenAI TTS]
K --> L[Telegram Voice Alert]
G --> M[Relay Disconnect Command]
The ESP32 sends measurement data to n8n through a Webhook. n8n’s Webhook node can receive external events and start a workflow; it can also return a response to the calling device. ThingSpeak’s Update API accepts HTTP GET or POST writes to channel fields.[mathworks][docs.n8n]
Hardware bill of materials
| Component | Quantity | Purpose |
|---|---|---|
| ESP32 DevKit V1 | 1 | Main controller, Wi-Fi, web server |
| PZEM-004T v3.0 | 1 | AC energy measurement |
| 5V 2A power supply | 1 | ESP32 power |
| HLK-PM01 / isolated 5V AC-DC module | 1 | Safe mains-to-5V conversion |
| 5V relay module | 1 | Optional load disconnect |
| OLED SSD1306 0.96" | 1 | Optional local display |
| Buzzer | 1 | Local theft alarm |
| Push button | 1 | Alarm mute/reset |
| Enclosure, DIN rail, terminal blocks | 1 set | Safe installation |
| CT / direct AC input as per PZEM model | 1 | Current sensing |
Use the PZEM-004T-100A version with an external current transformer for higher-current installations, or the 10A direct-input version only for low-current test loads.
Electrical schematic
AC MAINS 230V / 110V
|
+-----------------+------------------+
| |
LIVE (L) NEUTRAL (N)
| |
+------+-------+ +-------+--------+
| PZEM-004T | | PZEM-004T |
| AC INPUT L | | AC INPUT N |
+------+-------+ +-------+--------+
| |
+--------> LOAD LIVE <---------------+
|
AC LOAD
PZEM-004T TTL SIDE ESP32 GPIO
------------------ ----------
5V -----------------------------> VIN / 5V
GND -----------------------------> GND
TX -----------------------------> GPIO16 RX2
RX -----------------------------> GPIO17 TX2
Optional:
Relay IN ------------------------> GPIO26
Buzzer + ------------------------> GPIO25
Button --------------------------> GPIO27 to GND
OLED SDA ------------------------> GPIO21
OLED SCL ------------------------> GPIO22
The PZEM-004T communicates using Modbus RTU over UART. Connect its TX to ESP32 RX2 and RX to ESP32 TX2. Use a common ground and a stable 5V supply.
Theft-detection logic
The project uses several complementary conditions. A single suspicious event should not immediately disconnect a load; use confirmation windows and cooldowns.
| Detection type | Condition | Alert level |
|---|---|---|
| Sudden overload | Current rises above configured threshold for N samples | High |
| Abrupt load drop | Power falls by more than X% within Y seconds while voltage remains normal | Medium |
| Voltage-present but meter communication failure | Repeated Modbus read failures | High |
| Energy jump | Energy increases faster than expected for the connected load profile | High |
| Tamper / enclosure open | Optional magnetic reed switch opens | Critical |
| Power factor anomaly | PF changes abruptly with stable load | Medium |
| Night-time abnormal load | Load exceeds baseline during configured night window | High |
| Meter offline | ESP32 stops sending data to n8n for a configured period | Critical |
Example rule:
IF current > 15 A for 5 consecutive readings
OR energy_increment > expected_increment * 2
OR pzem_comm_failures >= 5
THEN
set theft_status = "SUSPECTED"
send webhook event to n8n
activate buzzer
after 3 confirmed alerts, activate relay cutoff
ESP32 firmware
Install these Arduino libraries:
WiFi.hWebServer.hHTTPClient.hArduinoJsonPZEM004Tv30
Complete ESP32 code
#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <PZEM004Tv30.h>
// ---------------- Wi-Fi ----------------
const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
// ---------------- n8n ----------------
const char* N8N_WEBHOOK =
"https://YOUR-N8N-DOMAIN/webhook/energy-meter";
// ---------------- ThingSpeak ----------------
const char* THINGSPEAK_KEY = "YOUR_THINGSPEAK_WRITE_API_KEY";
const char* THINGSPEAK_URL =
"http://api.thingspeak.com/update";
// ---------------- Pins ----------------
#define PZEM_RX_PIN 16
#define PZEM_TX_PIN 17
#define RELAY_PIN 26
#define BUZZER_PIN 25
#define RESET_BUTTON_PIN 27
// ---------------- Theft thresholds ----------------
float OVERCURRENT_LIMIT = 15.0;
float VOLTAGE_LOW_LIMIT = 180.0;
float VOLTAGE_HIGH_LIMIT = 250.0;
float SUDDEN_DROP_PERCENT = 60.0;
int OVERCURRENT_SAMPLES = 5;
int COMM_FAIL_LIMIT = 5;
// ---------------- Objects ----------------
PZEM004Tv30 pzem(Serial2, PZEM_RX_PIN, PZEM_TX_PIN);
WebServer server(80);
float voltage = 0, current = 0, power = 0, energy = 0;
float frequency = 0, pf = 0;
float previousPower = 0;
int overcurrentCount = 0;
int commFailCount = 0;
bool theftAlert = false;
bool relayCutoff = false;
unsigned long lastThingSpeak = 0;
unsigned long lastN8N = 0;
unsigned long lastTheftAlert = 0;
const unsigned long THINGSPEAK_INTERVAL = 20000; // 20 sec
const unsigned long N8N_INTERVAL = 30000; // 30 sec
const unsigned long THEFT_COOLDOWN = 120000; // 2 min
void setup() {
Serial.begin(115200);
Serial2.begin(9600, SERIAL_8N1, PZEM_RX_PIN, PZEM_TX_PIN);
pinMode(RELAY_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);
digitalWrite(RELAY_PIN, HIGH); // Relay module logic may be inverted
digitalWrite(BUZZER_PIN, LOW);
connectWiFi();
server.on("/", handleRoot);
server.on("/data", handleData);
server.on("/reset", handleReset);
server.on("/relay/on", []() {
relayCutoff = false;
digitalWrite(RELAY_PIN, HIGH);
server.send(200, "application/json", "{\"relay\":\"ON\"}");
});
server.on("/relay/off", []() {
relayCutoff = true;
digitalWrite(RELAY_PIN, LOW);
server.send(200, "application/json", "{\"relay\":\"OFF\"}");
});
server.begin();
Serial.println("ESP32 Energy Meter Started");
}
void loop() {
server.handleClient();
readEnergyMeter();
runTheftDetection();
unsigned long now = millis();
if (now - lastThingSpeak >= THINGSPEAK_INTERVAL) {
lastThingSpeak = now;
sendThingSpeak();
}
if (now - lastN8N >= N8N_INTERVAL) {
lastN8N = now;
sendToN8N("periodic");
}
if (digitalRead(RESET_BUTTON_PIN) == LOW) {
theftAlert = false;
overcurrentCount = 0;
commFailCount = 0;
digitalWrite(BUZZER_PIN, LOW);
delay(500);
}
}
void connectWiFi() {
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Dashboard: http://");
Serial.println(WiFi.localIP());
}
void readEnergyMeter() {
float v = pzem.voltage();
float c = pzem.current();
float p = pzem.power();
float e = pzem.energy();
float f = pzem.frequency();
float powerFactor = pzem.pf();
if (isnan(v) || isnan(c) || isnan(p)) {
commFailCount++;
Serial.println("PZEM communication failed");
if (commFailCount >= COMM_FAIL_LIMIT && !theftAlert) {
theftAlert = true;
sendToN8N("meter_communication_failure");
buzzerAlert();
}
return;
}
commFailCount = 0;
voltage = v;
current = c;
power = p;
energy = e;
frequency = f;
pf = powerFactor;
}
void runTheftDetection() {
// 1. Overcurrent detection
if (current > OVERCURRENT_LIMIT) {
overcurrentCount++;
} else {
overcurrentCount = 0;
}
if (overcurrentCount >= OVERCURRENT_SAMPLES) {
triggerTheft("overcurrent_detected");
}
// 2. Sudden power drop
if (previousPower > 0 && power > 0) {
float dropPercent =
((previousPower - power) / previousPower) * 100.0;
if (dropPercent >= SUDDEN_DROP_PERCENT) {
triggerTheft("sudden_power_drop");
}
}
// 3. Voltage anomaly
if (voltage > 0 &&
(voltage < VOLTAGE_LOW_LIMIT || voltage > VOLTAGE_HIGH_LIMIT)) {
triggerTheft("voltage_anomaly");
}
previousPower = power;
}
void triggerTheft(String reason) {
unsigned long now = millis();
if (theftAlert && (now - lastTheftAlert < THEFT_COOLDOWN)) {
return;
}
theftAlert = true;
lastTheftAlert = now;
Serial.println("THEFT ALERT: " + reason);
buzzerAlert();
sendToN8N(reason);
}
void buzzerAlert() {
for (int i = 0; i < 5; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(200);
digitalWrite(BUZZER_PIN, LOW);
delay(200);
}
}
void sendThingSpeak() {
if (WiFi.status() != WL_CONNECTED) return;
HTTPClient http;
String url = String(THINGSPEAK_URL) +
"?api_key=" + THINGSPEAK_KEY +
"&field1=" + String(voltage, 2) +
"&field2=" + String(current, 3) +
"&field3=" + String(power, 2) +
"&field4=" + String(energy, 3) +
"&field5=" + String(frequency, 2) +
"&field6=" + String(pf, 3) +
"&field7=" + String(theftAlert ? 1 : 0);
http.begin(url);
int code = http.GET();
http.end();
Serial.println("ThingSpeak response: " + String(code));
}
void sendToN8N(String eventType) {
if (WiFi.status() != WL_CONNECTED) return;
HTTPClient http;
http.begin(N8N_WEBHOOK);
http.addHeader("Content-Type", "application/json");
StaticJsonDocument<512> doc;
doc["device_id"] = "ESP32-ENERGY-01";
doc["event"] = eventType;
doc["voltage"] = voltage;
doc["current"] = current;
doc["power"] = power;
doc["energy"] = energy;
doc["frequency"] = frequency;
doc["power_factor"] = pf;
doc["theft_alert"] = theftAlert;
doc["relay_cutoff"] = relayCutoff;
String payload;
serializeJson(doc, payload);
int code = http.POST(payload);
http.end();
Serial.println("n8n response: " + String(code));
}
void handleData() {
String json = "{";
json += "\"voltage\":" + String(voltage, 2) + ",";
json += "\"current\":" + String(current, 3) + ",";
json += "\"power\":" + String(power, 2) + ",";
json += "\"energy\":" + String(energy, 3) + ",";
json += "\"frequency\":" + String(frequency, 2) + ",";
json += "\"power_factor\":" + String(pf, 3) + ",";
json += "\"theft_alert\":" + String(theftAlert ? "true" : "false") + ",";
json += "\"relay_cutoff\":" + String(relayCutoff ? "true" : "false");
json += "}";
server.sendHeader("Access-Control-Allow-Origin", "*");
server.send(200, "application/json", json);
}
void handleReset() {
theftAlert = false;
overcurrentCount = 0;
commFailCount = 0;
digitalWrite(BUZZER_PIN, LOW);
server.send(200, "application/json", "{\"status\":\"reset\"}");
}
void handleRoot() {
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>IoT Energy Meter</title>
<style>
body { font-family: Arial; background:#0f172a; color:#e2e8f0; margin:0; padding:20px; }
.card { background:#1e293b; padding:18px; border-radius:14px; margin-bottom:14px; }
h1 { color:#38bdf8; }
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px; }
.value { font-size:26px; font-weight:bold; color:#facc15; }
.alert { color:#ef4444; font-weight:bold; }
button { padding:10px 16px; border:none; border-radius:8px; background:#38bdf8; color:#000; font-weight:bold; cursor:pointer; }
</style>
</head>
<body>
<h1>⚡ Intelligent IoT Energy Meter</h1>
<div class="card">
<div class="grid">
<div>Voltage<div class="value" id="v">--</div> V</div>
<div>Current<div class="value" id="c">--</div> A</div>
<div>Power<div class="value" id="p">--</div> W</div>
<div>Energy<div class="value" id="e">--</div> kWh</div>
<div>Frequency<div class="value" id="f">--</div> Hz</div>
<div>Power Factor<div class="value" id="pf">--</div></div>
</div>
</div>
<div class="card">
<h3>Security Status</h3>
<div id="status">Checking...</div>
<br>
<button onclick="location.href='/reset'">Reset Alert</button>
<button onclick="location.href='/relay/off'">Relay OFF</button>
<button onclick="location.href='/relay/on'">Relay ON</button>
</div>
<script>
setInterval(async () => {
const r = await fetch('/data');
const d = await r.json();
v.innerText = d.voltage;
c.innerText = d.current;
p.innerText = d.power;
e.innerText = d.energy;
f.innerText = d.frequency;
pf.innerText = d.power_factor;
status.innerHTML = d.theft_alert
? '<span class="alert">⚠ THEFT / ANOMALY DETECTED</span>'
: '✅ System Normal';
}, 2000);
</script>
</body>
</html>
)rawliteral";
server.send(200, "text/html", html);
}
The firmware exposes:
http://ESP32_IP/— dashboard.http://ESP32_IP/data— JSON API.http://ESP32_IP/reset— clear local alert.http://ESP32_IP/relay/onand/relay/off— optional relay control.
ThingSpeak setup
- Create a free ThingSpeak account and create a new channel.
- Enable seven fields:
| Field | Data |
|---|---|
| field1 | Voltage |
| field2 | Current |
| field3 | Power |
| field4 | Energy |
| field5 | Frequency |
| field6 | Power factor |
| field7 | Theft alert flag |
- Copy the channel’s Write API Key into
THINGSPEAK_KEY. - ThingSpeak’s REST API supports writing channel data with HTTP GET or POST; each channel supports numeric fields plus status and location metadata.[mathworks][github]
- Use the channel’s private/public view or MATLAB Visualizations for graphs.
ThingSpeak’s free tier commonly limits update frequency, so the code uses a 20-second interval. Adjust this according to your account limits.
n8n automation design
Main workflow
flowchart TD
A[ESP32 HTTP POST] --> B[n8n Webhook]
B --> C[Set / Normalize JSON]
C --> D{Event type?}
D -- periodic --> E[Google Sheets Append]
D -- theft/anomaly --> F[Google Sheets Append]
F --> G[AI Agent: Analyze Event]
G --> H[Generate Alert Message]
H --> I[Gmail Send Alert]
H --> J[Telegram Send Text]
H --> K[OpenAI Text-to-Speech]
K --> L[Convert / Prepare OGG or MP3]
L --> M[Telegram Send Voice]
n8n’s Telegram integration supports sending messages and audio; Telegram’s Bot API sendVoice displays an audio file as a playable voice note when it is OGG/Opus, MP3, or M4A.[docs.n8n][core.telegram]
Google Sheet columns
Create a Google Sheet named IoT_Energy_Meter_Log with this header row:
Timestamp | Device ID | Event | Voltage | Current | Power | Energy | Frequency | Power Factor | Theft Alert | Relay Cutoff | AI Summary
n8n workflow nodes
1. Webhook
- Method: POST
- Path:
energy-meter - Response mode: Immediately or “Using Respond to Webhook”
- Production URL example:
https://your-n8n-domain/webhook/energy-meter
n8n Webhook nodes are designed to receive data from external services and start workflows.[docs.n8n]
2. Set node — Normalize data
Create fields:
device_id = {{$json.body.device_id}}
event = {{$json.body.event}}
voltage = {{$json.body.voltage}}
current = {{$json.body.current}}
power = {{$json.body.power}}
energy = {{$json.body.energy}}
frequency = {{$json.body.frequency}}
power_factor = {{$json.body.power_factor}}
theft_alert = {{$json.body.theft_alert}}
relay_cutoff = {{$json.body.relay_cutoff}}
timestamp = {{$now}}
3. IF node — Is alert?
Condition:
event is not equal to periodic
OR theft_alert is true
If false, append a normal reading to Google Sheets.
If true, continue to AI analysis and notifications.
4. Google Sheets node
- Operation: Append Row
- Document:
IoT_Energy_Meter_Log - Sheet:
Sheet1 - Map each normalized field to its column.
5. AI Agent node
Use an OpenAI Chat Model or another LLM node with this system prompt:
You are an electrical safety and energy-monitoring assistant.
Analyze IoT energy-meter telemetry and identify the likely cause
of the alert.
Return a short, clear alert suitable for Telegram and email.
Mention:
1. What happened
2. Possible reason
3. Recommended action
4. Risk level: LOW, MEDIUM, HIGH, or CRITICAL
Do not provide instructions that require touching live electrical
equipment. Always recommend contacting a qualified electrician for
mains-side inspection.
User prompt:
Device: {{ $json.device_id }}
Event: {{ $json.event }}
Voltage: {{ $json.voltage }} V
Current: {{ $json.current }} A
Power: {{ $json.power }} W
Energy: {{ $json.energy }} kWh
Frequency: {{ $json.frequency }} Hz
Power Factor: {{ $json.power_factor }}
Theft Alert: {{ $json.theft_alert }}
6. Gmail node
- To: your email address
- Subject:
⚠ IoT Energy Alert — {{ $json.event }} - Body: AI-generated alert plus raw telemetry.
7. Telegram node — Text alert
- Resource: Message
- Operation: Send Message
- Chat ID: your Telegram chat ID
- Text: AI alert summary
n8n supports Telegram message operations and uses Telegram bot credentials.[docs.n8n][docs.n8n]
8. OpenAI Text-to-Speech node
Generate an MP3 voice file from the AI alert text. Then send it through Telegram.
9. Telegram node — Voice alert
Use the Telegram node’s Send Audio operation, or call Telegram’s sendVoice through an HTTP Request node. Telegram treats suitable audio as a playable voice message; OGG/Opus, MP3, and M4A are supported formats.[docs.n8n][core.telegram]
Telegram bot setup
- Open Telegram and search for @BotFather.
- Send
/newbot. - Choose a bot name and username.
- Copy the bot token.
- In n8n, create Telegram credentials using that token.
- Message your bot once, then obtain your chat ID from n8n execution data or a Telegram
getUpdatesAPI call. - Put the chat ID into the Telegram nodes.
For incoming commands such as /status, add a second workflow:
flowchart LR
A[Telegram Trigger] --> B{Message type}
B -- /status --> C[HTTP GET ESP32 /data]
C --> D[Format Reply]
D --> E[Telegram Send Message]
B -- /help --> F[Send Command Help]
Example n8n workflow JSON
Import this skeleton into n8n, then connect your own Telegram, Gmail, Google Sheets, and OpenAI credentials.
{
"name": "ESP32 Intelligent Energy Meter and Theft Detection",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "energy-meter",
"responseMode": "responseNode"
},
"id": "webhook-node",
"name": "ESP32 Data Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [0, 0],
"webhookId": "energy-meter"
},
{
"parameters": {
"assignments": {
"assignments": [
{ "name": "device_id", "value": "={{ $json.body.device_id }}" },
{ "name": "event", "value": "={{ $json.body.event }}" },
{ "name": "voltage", "value": "={{ $json.body.voltage }}" },
{ "name": "current", "value": "={{ $json.body.current }}" },
{ "name": "power", "value": "={{ $json.body.power }}" },
{ "name": "energy", "value": "={{ $json.body.energy }}" },
{ "name": "frequency", "value": "={{ $json.body.frequency }}" },
{ "name": "power_factor", "value": "={{ $json.body.power_factor }}" },
{ "name": "theft_alert", "value": "={{ $json.body.theft_alert }}" },
{ "name": "relay_cutoff", "value": "={{ $json.body.relay_cutoff }}" },
{ "name": "timestamp", "value": "={{ $now }}" }
]
}
},
"id": "normalize-node",
"name": "Normalize Telemetry",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [220, 0]
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "version": 2 },
"conditions": [
{
"leftValue": "={{ $json.event }}",
"rightValue": "periodic",
"operator": { "type": "string", "operation": "notEquals" }
}
],
"combinator": "or"
}
},
"id": "alert-check",
"name": "Is Theft or Anomaly?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [440, 0]
}
],
"connections": {
"ESP32 Data Webhook": {
"main": [[{ "node": "Normalize Telemetry", "type": "main", "index": 0 }]]
},
"Normalize Telemetry": {
"main": [[{ "node": "Is Theft or Anomaly?", "type": "main", "index": 0 }]]
}
}
}
After importing, add:
- Google Sheets append on the false branch.
- AI Agent, Gmail, Telegram text, OpenAI TTS, and Telegram voice nodes on the true branch.
- A final Respond to Webhook node returning:
{
"status": "received",
"alert_processed": true
}
n8n’s Respond to Webhook node lets you control the HTTP response returned to the ESP32.[docs.n8n][github]
Testing procedure
- Connect the PZEM-004T to a low-power AC load, such as a 60 W bulb or fan.
- Power the ESP32 and open its dashboard at the IP address printed in Serial Monitor.
- Verify voltage, current, power, and energy readings.
- Confirm ThingSpeak fields update every 20 seconds.
- Send a test POST to n8n using:
curl -X POST https://YOUR-N8N-DOMAIN/webhook/energy-meter \
-H "Content-Type: application/json" \
-d '{
"device_id": "ESP32-ENERGY-01",
"event": "test_alert",
"voltage": 230.5,
"current": 12.8,
"power": 2940,
"energy": 1.245,
"frequency": 50.0,
"power_factor": 0.94,
"theft_alert": true,
"relay_cutoff": false
}'
- Verify Google Sheets row creation, Gmail delivery, Telegram text, and Telegram voice playback.
- Increase load gradually to trigger the overcurrent rule.
- Disconnect the PZEM communication wires briefly to test meter-failure detection.
Calibration and tuning
- Set
OVERCURRENT_LIMITbased on your actual circuit breaker and load rating. - Use at least 5–10 samples before triggering an alert to avoid false positives.
- Keep a 2–5 minute cooldown between repeated alerts.
- For theft detection, collect several days of normal consumption data first.
- Use a separate “expected load profile” for day, night, weekday, and weekend periods.
- Do not rely on relay cutoff alone; treat it as a protective action after confirmation.
Possible enhancements
- Add MQTT instead of HTTP for lower-latency communication.
- Store data in Firebase, Supabase, InfluxDB, or MySQL.
- Add a reed switch for physical enclosure tamper detection.
- Add a DS3231 RTC for offline time-stamping.
- Add multiple PZEM sensors for per-room monitoring.
- Use an AI model to learn normal daily consumption and detect deviations.
- Add WhatsApp, SMS, or phone-call alerts through Twilio or similar services.
- Add OTA firmware updates for remote maintenance.
This design gives you a complete AI-powered agentic IoT energy monitoring and theft-detection system: ESP32 performs edge measurement and first-level detection, while n8n acts as the automation and AI decision layer for cloud logging, email, Telegram, and voice notifications.



