Tuesday, 27 October 2026

Intelligent IoT Energy Meter with Theft Detection | ESP32 + n8n + Telegram, Gmail & AI Voice Alerts

Intelligent IoT Energy Meter with Electricity Theft Detection

ESP32 + n8n Automation + AI Agent + Telegram Voice Alerts + Gmail + Google Sheets + ThingSpeak + Web Dashboard

IoT Smart Energy Meter
DIY Smart Energy Meter With ESP32 + Home Assistant : 11 Steps (with Pictures) - Instructables
ThingsBoard Installations | ThingsBoard

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:

  1. Electrical measurement layer: sensors measure voltage, current, power, and energy.

  2. Edge-computing layer: the ESP32 collects readings, checks local safety thresholds, and transmits data.

  3. Cloud and automation layer: n8n receives readings and executes detection, logging, and notification workflows.

  4. AI communication layer: an AI agent explains incidents in plain language and prepares a short voice-alert script.

  5. 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.

— Arduino ESP32 latest documentation
+2

 

End-to-end data flow

mermaid
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:

ΔI=∣Iupstream−Idownstream∣\Delta I = |I_{\mathrm{upstream}}-I_{\mathrm{downstream}}|

Flag a possible discrepancy when:

ΔI>Ithreshold\Delta I > I_{\mathrm{threshold}}

for a configurable duration, provided that both sensors are healthy and the readings are time-aligned.

For an independent energy comparison:

ΔE=Ereference−Emeter\Delta E = E_{\mathrm{reference}}-E_{\mathrm{meter}}

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:

EkWh=∑PiΔti3,600,000E_{\mathrm{kWh}}=\frac{\sum P_i\Delta t_i}{3{,}600{,}000}

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.

OpenEnergyMonitor 0.0.1 documentation
+1

 

3. Hardware requirements

Probots ESP32 30 Pin CP2102 Development Board - WiFi & Bluetooth IoT Module Buy Online India
Jual PZEM 004T PZEM-004T versi 3.0 100A Sensor Arus / Energy Meter AC | Shopee Indonesia
Remote-controlled Lora room thermostats | Hackaday.io

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.

text
                 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

  1. Install the Arduino IDE from the official Arduino website.

  2. Install the ESP32 board package using the Espressif Arduino documentation.

  3. Select the correct ESP32 board and serial port.

  4. Install the PZEM004Tv30 library using the Library Manager, or use the maintained library version compatible with your hardware.

  5. Compile a basic serial example before connecting the cloud services.

  6. 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.

cpp

#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:

  1. Replace the Wi-Fi credentials.

  2. Set N8N_WEBHOOK to your real production webhook URL.

  3. Enter your ThingSpeak write key.

  4. Configure the current threshold based on the installed circuit and sensor rating. The example's 15 A is not a universal safe limit.

  5. Verify that your library constructor matches the installed version.

  6. Configure certificate validation instead of setInsecure() before deployment.

  7. Add device authentication, request validation, and a retry queue for production.

  8. 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.

MATLAB & Simulink

 

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

  1. Create a channel and enable the fields.

  2. Copy the channel's Write API Key.

  3. Copy the channel ID and use it for the dashboard.

  4. Enter the write key in the firmware.

  5. Upload a few test readings.

  6. Confirm that the channel updates.

  7. 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.

n8n Docs
+2

 

Workflow 1: telemetry ingestion and alerting

Node sequence

  1. Webhook — energy-meter

    Method: POST. Configure a production URL and an appropriate response mode.

  2. Code or Edit Fields — validate payload

    Check device_id, numeric values, timestamps or sequence numbers, and required fields. Reject malformed or unauthorized requests.

  3. Switch / IF — evaluate deterministic rules

    Branch on tamper, sensor_fault, high_current, and additional reference-meter discrepancy fields if implemented.

  4. Google Sheets — append row

    Save the timestamp, device identifier, electrical readings, event, and alert status.

  5. ThingSpeak — HTTP Request (optional)

    Write additional channel fields not sent directly by the ESP32.

  6. AI Agent or language-model node

    Generate a concise explanation and recommended inspection steps from validated measurements.

  7. 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.

javascript

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

mermaid
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:

  1. Read the day's data from your chosen storage.

  2. Calculate energy consumed, peak measured power, and alert count.

  3. Compare against the previous day.

  4. Generate a brief AI summary.

  5. Append a report to Google Sheets.

  6. 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.

text

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.

GitHub
+1

 

Example agent input

json
{
  "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

json
{
  "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.

GitHub
+1

 

10.1 Create the Telegram bot

  1. Open Telegram and search for @BotFather.

  2. Start a conversation and use /newbot.

  3. Give your bot a display name and a unique username.

  4. Save the generated bot token in a secure location.

  5. Open your new bot and press Start.

  6. Obtain the destination chat ID using an authorized bot update or n8n's Telegram integration.

  7. 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

Telegram: что это, описание мессенджера, функции, блокировки, работа в России

Smart Energy Meter

Example notification

Warning

⚡ 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:

text
⚡ 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:

mermaid
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:

  1. Add a text-to-speech provider node or an HTTP Request node.

  2. Send the voice_script as the input text.

  3. Request audio in a format supported by the provider.

  4. Receive the generated audio as binary data or a file URL.

  5. Pass the binary data to the Telegram node's audio operation.

  6. Use a voice-message operation if you need Telegram's voice-note presentation and the audio is encoded in a compatible format.

  7. 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.

Power measurement
Wyldhyde Technologies
AutoEdge - AI-Powered Manufacturing Intelligence

13.1 Recommended webpage layout

Smart Energy Monitor

Prototype dashboard preview

Demo data

Voltage

231.8 V

Current

16.2 A

Active power

3.50 kW

Energy consumed

42.61 kWh

Current trend — sample data

Current over time

Illustrative values for the dashboard mock-up, not actual device telemetry.

Expand chart
  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.

mermaid
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.

mermaid
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.

  1. Phase 1 — Hardware setup

    Assemble the ESP32 and low-voltage interface. Have the mains measurement installation checked by a qualified electrician.

  2. Phase 2 — Local firmware

    Upload the firmware and confirm that readings are stable in the Arduino Serial Monitor.

  3. Phase 3 — ThingSpeak

    Create the channel and verify that voltage, current, power, and energy are being recorded.

  4. Phase 4 — n8n webhook

    Create the webhook, send a test JSON payload, validate it, and confirm successful execution.

  5. Phase 5 — Google Sheets

    Connect Google credentials and confirm that telemetry rows and incident records are appended correctly.

  6. Phase 6 — Notifications

    Configure Telegram and Gmail, then test each notification independently.

  7. Phase 7 — AI and voice

    Add the AI explanation step, connect a text-to-speech provider, and test the complete voice-delivery branch.

  8. Phase 8 — Web dashboard

    Build the webpage, connect its secured read-only endpoint, and verify its data against the stored measurements.

  9. 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.

Probots ESP32 Wireless Bluetooth 38Pin Development Board with CP2102 Type-C USB Interface Buy Online India

ESP32 firmware and Wi-Fi

Official Arduino core setup, networking, and hardware references.

Arduino-ESP32 documentation 

รู้จัก n8n

n8n automation and AI

Webhooks, workflow nodes, integrations, and agent configuration.

n8n documentation  · Webhook node 

ThingSpeak Cloud Platform in IoT: Features, Working, and Applications Explained
 – Electro Robo Store

ThingSpeak IoT cloud

Channel creation and data upload using REST or MQTT.

Write data to ThingSpeak 

IotaWatt 4.0 - Page 7 - IoTaWatt - OpenEnergyMonitor Community

Energy measurement principles

Current sensing, voltage measurement, calibration, and sources of error.

Current and voltage measurement guide 

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.h
  • WebServer.h
  • HTTPClient.h
  • ArduinoJson
  • PZEM004Tv30

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/on and /relay/off — optional relay control.

ThingSpeak setup

  1. Create a free ThingSpeak account and create a new channel.
  2. Enable seven fields:
Field Data
field1 Voltage
field2 Current
field3 Power
field4 Energy
field5 Frequency
field6 Power factor
field7 Theft alert flag
  1. Copy the channel’s Write API Key into THINGSPEAK_KEY.
  2. 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]
  3. 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

  1. Open Telegram and search for @BotFather.
  2. Send /newbot.
  3. Choose a bot name and username.
  4. Copy the bot token.
  5. In n8n, create Telegram credentials using that token.
  6. Message your bot once, then obtain your chat ID from n8n execution data or a Telegram getUpdates API call.
  7. 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

  1. Connect the PZEM-004T to a low-power AC load, such as a 60 W bulb or fan.
  2. Power the ESP32 and open its dashboard at the IP address printed in Serial Monitor.
  3. Verify voltage, current, power, and energy readings.
  4. Confirm ThingSpeak fields update every 20 seconds.
  5. 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
}'
  1. Verify Google Sheets row creation, Gmail delivery, Telegram text, and Telegram voice playback.
  2. Increase load gradually to trigger the overcurrent rule.
  3. Disconnect the PZEM communication wires briefly to test meter-failure detection.

Calibration and tuning

  • Set OVERCURRENT_LIMIT based 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.