Creating a DIY device for monitoring power consumption using ESP32

ESP32-Energy-Monitoring-Device

Electrical appliances are an integral part of our daily lives, ranging from the essential smartphone charger to larger devices like room heaters, air conditioners, and washing machines. Given the current global energy crisis, it has become crucial to gain insights into the power consumption of these appliances. While our monthly electricity bills offer a general overview of our cumulative energy usage, they provide limited information about the real-time power consumption of individual devices. To accurately measure and monitor power usage, it’s essential to detect both voltage and current draw from our appliances.

For this purpose, commercial energy monitoring devices are readily available in the market, but they often come with a hefty price tag and lack the hands-on learning and enjoyable experience that comes with a DIY approach.

In this article, we’ll explore how to create a straightforward device for monitoring power consumption. This project will utilize an ESP32 and readily accessible sensors to achieve this goal

Materials needed to construct a Smart Power Consumption Meter

  1. ESP32 WROOM 32D Module
  2. HI LINK 5V 3W SMPS
  3. 0.96” 128X64 I2C LED
  4. ZMPT101B Voltage Sensor
  5. ACS712 Current Sensor
  6. 220V AC 3 Pin Socket MALE
  7. 220V AC 3 Pin Socket FEMALE
  8. 3D Printed Casing

The functioning principle of the Smart Power Consumption Meter

To initiate the project, our central objective is to relay voltage and current information to the ESP32 microcontroller. The ESP32 will then handle this data collected from the sensors and transmit it to the OLED Display, allowing the user to visualize energy consumption. Additionally, to power all these components, we will incorporate a HI-Link 5V SMPS Module, which effectively converts 220V AC into 5V DC.

ZMPT101B Voltage Sensor

The ZMPT101B is a Voltage transformer sensor that simply scales down the voltage linearly and provides an analog output to the user. It also provides an isolation voltage of up to 4000V and a safe operating voltage of 1000V.

Voltage-Sensor-Schematic

Upon examining the schematic, it becomes apparent that the ZMPT Transformer’s output is directed to an LM358N Low Power Dual Op-Amp IC, which serves to amplify the voltage peaks.

ACS712 20A Current Sensor

The ACS712 is an integrated circuit designed for current sensing, utilizing Hall effect sensors. It incorporates a Hall effect IC that registers alterations in the magnetic field as current flows through, converting this magnetic field variation into a voltage proportional to the current. This voltage is then outputted to the user. Examining the Sensor Module schematic reveals that the IC is equipped with nearly all the necessary components integrated within it, requiring only a minimal number of passive components to operate.

Current-Sensor-Schematic-Diagram

Among the three available variants of the IC (5A, 20A, 30A), we opted for the 20A version. This choice aligns with our current measurement needs, offering a balance between capacity and resolution that suits our purposes well.

Diagram for Connecting the Power Consumption Monitoring Device

Energy-Monitoring-Device-Circuit-

The diagram illustrates the parallel connection of the Hi-link SMPS and the input terminal of the voltage sensor (ZMPT101B) to the AC Live and AC Neutral, with the current sensor (ACS712) forming a series connection with the live AC wire

The OLED display is linked through the ESP32’s I2C pins, while the Voltage and Current sensors are attached to Pin 34 and Pin 36 (VP), respectively. These pins have the capability to perform analog-to-digital conversion using the 12-BIT ADC integrated into the ESP32

All the components receive power from the 5V output of the Hi-Link module through the Vo+ and Vo- terminals

Programming code for the Power Consumption Monitoring Device

Installing Required Libraries

We initiate the process by installing the Adafruit GFX and Adafruit SSD 1306 Library. You can conveniently access the library manager within the Arduino IDE by navigating to the tools menu

Arduino-IDE-Library-Manager

We locate Adafruit GFX and Adafruit SSD1306 and proceed to install them with a simple click on the “Install” button.

Adafruit-GFX-and-Adafruit-SSD1306-Library

Next, we need to install the libraries for the ZMPT101B and ACS712 current sensor modules. These libraries share a similar codebase, and while you can find the original libraries [here], we’ve made slight modifications to adapt them to the new microcontroller and ensure compatibility with the ESP32’s 12-bit 3.3V ADC.

The installation procedure for both libraries follows the same steps. Just visit the respective libraries’ GitHub repository and click on the “Download ZIP” button located under the dropdown menu of the “Code” button.

Github-Download-Zip-Option

After the ZIP file has been downloaded, open the Arduino IDE and navigate to Sketch >> Include Library >> Add ZIP Library.

Github-Add-Zip-File

Find the downloaded ZIP file for the library and then click on it to open.

ZMPT1018-Arduino-IDE

The Arduino IDE will proceed to install the library. You can apply the same procedure for both libraries.

Once these libraries are successfully installed, we can readily include them at the start of our code.

#include "ZMPT101B.h"
#include "ACS712.h"
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

The Wire and SPI libraries come pre-installed in the IDE and are essential for the operation of the I2C bus, which is crucial for the OLED Display’s functionality.

Initialization Commands

Now, we proceed to initialize objects for both the current and voltage sensors as follows:

ZMPT101B voltageSensor(34);
ACS712 currentSensor(ACS712_20A, 36);

Furthermore, we initiate our OLED display using the subsequent lines of code

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

Subsequently, we declare and initialize several global variables that will prove essential in our code as we progress.

float P=0;
float U=0;
float I=0;
long dt=0;
float CulmPwh=0;
float units=0;    
long changeScreen=0;
float lastSample=0;
unsigned long lasttime=0;
long ScreenSelect = 0;

Void Setup Code

Within the setup code, we establish the zero point and sensitivity parameters for our sensors, initiating the display as well.

Please take note: While the sensitivity value within the code should remain unchanged, it may be necessary to adjust the zero point. To perform this adjustment, uncomment the lines of code labeled as “Calibration Commands” one by one, making sure there is no current or voltage present on the module. The code will then display a zero point on the screen, which you can use to update the zero point value in the code

The calibration process must be conducted independently for both the Current and Voltage Sensors.

void setup()
{
  Serial.begin(9600);
  delay(100);
  voltageSensor.setSensitivity(0.0025);
  voltageSensor.setZeroPoint(2621);
  currentSensor.setZeroPoint(2943);
  currentSensor.setSensitivity(0.15);
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); }
  // Clear the buffer
  display.clearDisplay();
  display.display();
  //Caliberation Commands Need To Be Run On First Upload. 
  //CalibCurrent();
  //CalibVoltage();
}

Void Loop Code

Within the loop code, our task is straightforward: we read data from the sensors utilizing the analog read command and transmit it to a custom display function at regular 500-millisecond intervals.

Additionally, we conduct calculations for Instantaneous Power and Energy Consumed.

Given the multitude of variables requiring display to the user (Voltage, Current, Instantaneous Power, Energy Consumed, Units Consumed), we’ve implemented a basic code mechanism for toggling between four screens, each showcasing the relevant information.

void loop()
{
  // To measure voltage/current we need to know the frequency of voltage/current
  // By default 50Hz is used, but you can specify desired frequency
  // as first argument to getVoltageAC and getCurrentAC() method, if necessary
  U = voltageSensor.getVoltageAC();
    if(U<55)
      {
        U=0;
        CulmPwh=0;
       }
  I = currentSensor.getCurrentAC();
  dt = micros()- lastSample;
    if(I<0.15)
      {
        I=0;
        CulmPwh=0;
      }
  // To calculate the power we need voltage multiplied by current
  P = U * I;
  CulmPwh = CulmPwh + P*(dt/3600);///uWh
  units= CulmPwh/1000;
  if(millis()-changeScreen>5000)
    {
      ScreenSelect+=1;
      changeScreen=millis();
    }
  if(millis()-lasttime>500)
    {
      if((ScreenSelect%4)==0)
      { displayVoltCurrent(); }//Volts and Current
      else if( (ScreenSelect%4)==1)
      { displayInstPower();   }//Instantaenous Power
      else if( (ScreenSelect%4)==2)
      { displayEnergy();      } //Energy
      else if( (ScreenSelect%4)==3)
      { displayUnits();       } //Units
    }
  lastSample=micros();
}

User Defined Functions

Reviewing the provided code, it becomes evident that we have employed functions such as:

CalibCurrent();

CalibVoltage();
displayVoltCurrent();
displayInstPower();
displayEnergy();
displayUnits();
displayCenter(String,Position);

These functions are not part of the library’s built-in functions; rather, we have written them ourselves. Their purpose is to facilitate data transmission to the OLED Module and maintain code organization. These custom functions can be placed following the void loop() function.

void displayVoltCurrent()
{
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(0,0);
void displayVoltCurrent()
{
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(3);
  displayCenter(String(U)+"V",3);
  display.setTextSize(3);
  displayCenter(String(I)+"A",33);
  display.display();
  lasttime=millis();
}
void displayInstPower()
{
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(2);
  display.setCursor(0,0);
  displayCenter("Power",3);
  display.setTextSize(3);
     if(P>1000)
      {
        displayCenter(String(P/1000)+"kW",30);
      }
    else
      {
        displayCenter(String(P)+"W",30);
      }
  display.display();
  lasttime=millis();
}
void displayEnergy()
{
  display.clearDisplay();
  display.setTextColor(WHITE);
   if(CulmPwh>1000000000)
     {
        display.setTextSize(2);
        displayCenter("Energy kWh",3);
        display.setTextSize(3);     
        displayCenter(String(CulmPwh/1000000000),30);
     }
   else if(CulmPwh<1000000000 && CulmPwh>1000000)
     {
        display.setTextSize(2);
        displayCenter("Energy Wh",3);
        display.setTextSize(3);     
        displayCenter(String(CulmPwh/1000000),30);
     }
   else if(CulmPwh<1000000 && CulmPwh>1000)
     {
        display.setTextSize(2)
        displayCenter("Energy mWh",3);
        display.setTextSize(3);     
        displayCenter(String(CulmPwh/1000),30);
     }
   else
     {
        display.setTextSize(2);
        displayCenter("Energy uWh",3);
        display.setTextSize(3);     
        displayCenter(String(CulmPwh),30);
     }
  display.display();
  lasttime=millis();
 }
void displayUnits()
{
  display.clearDisplay();
  display.setTextColor(WHITE);
    if(units>1000000)
      {
        display.setTextSize(2);
        displayCenter("Units",3);
        display.setTextSize(3);     
        displayCenter(String(units/1000000),30);
      }
    else if(units<1000000 && units>1000)
      {
        display.setTextSize(2);
        displayCenter("MilliUnits",3);
        display.setTextSize(3);     
        displayCenter(String(units/1000),30);
      }
  else
      {
        display.setTextSize(2);
        displayCenter("MicroUnits",3);
        display.setTextSize(3);     
        displayCenter(String(units),30);
      }
  display.display();
  lasttime=millis();
 }
void CalibCurrent()
{
  while(1)
  {
    currentSensor.calibrate(); 
    Serial.print("Zero Point Current :");
    Serial.println(currentSensor.getZeroPoint());
    display.clearDisplay();
    display.setTextColor(WHITE);
    display.setTextSize(1);
    display.setCursor(0,0);
    display.print("Current Zero Point :");
    display.setCursor(0,20);
    display.setTextSize(2);
    display.print(currentSensor.getZeroPoint());
    display.display();
    delay(500);
  }
}
void CalibVoltage()
{
  while(1)
  {
    voltageSensor.calibrate(); 
    Serial.print("Zero Point Voltage :");
    Serial.println(voltageSensor.getZeroPoint());
    display.clearDisplay();
    display.setTextColor(WHITE);
    display.setTextSize(1);
    display.setCursor(0,0);
    display.print("Voltage Zero Point :");
    display.setCursor(0,20);
    display.setTextSize(2);
    display.print(voltageSensor.getZeroPoint());
    display.display();
    delay(500);
  }
}
void displayCenter(String text, int line)
{
  int16_t x1;
  int16_t y1;
  uint16_t width;
  uint16_t height;
  display.getTextBounds(text, 0, 0, &x1, &y1, &width, &height);
  // display on horizontal center
  display.setCursor((SCREEN_WIDTH - width) / 2, line);
  display.println(text); // text to display
  display.display();
}

Assembly of the Energy Monitoring System

After successfully uploading the code to the ESP32, you can commence the project’s construction by 3D printing an enclosure for the system. We have provided the STL files for the enclosure, which consists of two parts that can be joined after assembly.

Please follow the outlined procedure to securely position all the components for a neat and secure fit. It is advisable to take precautions like using heat shrink tubing and insulation tape to ensure proper insulation, preventing any potential short circuits.

ESP32-Energy-Monitoring-Device (1)

Code

#include "ZMPT101B.h"
#include "ACS712.h"

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);



ZMPT101B voltageSensor(34);
ACS712 currentSensor(ACS712_20A, 36);

float P=0;
float U=0;
float I=0;
long dt=0;
float CulmPwh=0;
float units=0;
long changeScreen=0;
float lastSample=0;

unsigned long lasttime=0;
long ScreenSelect = 0;


void setup()
{
  Serial.begin(9600);

  delay(100);
  voltageSensor.setSensitivity(0.0025);
  voltageSensor.setZeroPoint(2621);
 
  currentSensor.setZeroPoint(2943);
  currentSensor.setSensitivity(0.15);

  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); }

  // Clear the buffer
  display.clearDisplay();
  display.display();

  //Caliberation Commands Need To Be Run On First Upload.  
  //CalibCurrent();
  //CalibVoltage();

}


void loop()
{
  // To measure voltage/current we need to know the frequency of voltage/current
  // By default 50Hz is used, but you can specify desired frequency
  // as first argument to getVoltageAC and getCurrentAC() method, if necessary

  U = voltageSensor.getVoltageAC();
    if(U<55)
      {
        U=0;
        CulmPwh=0;
       }
  
  I = currentSensor.getCurrentAC();
  dt = micros()- lastSample;
  
    if(I<0.15)
      {
        I=0;
        CulmPwh=0;
      }
  
  // To calculate the power we need voltage multiplied by current
  P = U * I;
  
  CulmPwh = CulmPwh + P*(dt/3600);///uWh
  units= CulmPwh/1000;

  if(millis()-changeScreen>5000)
    {
      ScreenSelect+=1;
      changeScreen=millis();
    }
    
  if(millis()-lasttime>500)
    {
      if((ScreenSelect%4)==0)
      { displayVoltCurrent(); }//Volts and Current
  
      else if( (ScreenSelect%4)==1)
      { displayInstPower();   }//Instantaenous Power
  
      else if( (ScreenSelect%4)==2)
      { displayEnergy();      } //Energy
      
      else if( (ScreenSelect%4)==3)
      { displayUnits();       } //Units
    }
  lastSample=micros();
}

void displayVoltCurrent()
{
  display.clearDisplay();
  display.setTextColor(WHITE);
  
  display.setTextSize(3);
  displayCenter(String(U)+"V",3);

  display.setTextSize(3);
  displayCenter(String(I)+"A",33);
  display.display();
  lasttime=millis();
  
}

void displayInstPower()
{
  display.clearDisplay();
  display.setTextColor(WHITE); 
  display.setTextSize(2);
  display.setCursor(0,0);
  displayCenter("Power",3); 
  display.setTextSize(3);
  
     if(P>1000)
      {
        displayCenter(String(P/1000)+"kW",30);
      }
    else
      {
        displayCenter(String(P)+"W",30);
      }

  display.display();
  lasttime=millis();
} 

void displayEnergy()
{
  display.clearDisplay();
  display.setTextColor(WHITE);
  
   if(CulmPwh>1000000000)
     {
        display.setTextSize(2);
        displayCenter("Energy kWh",3);
        display.setTextSize(3);      
        displayCenter(String(CulmPwh/1000000000),30);
     }
   else if(CulmPwh<1000000000 && CulmPwh>1000000)
     {
        display.setTextSize(2);
        displayCenter("Energy Wh",3);
        display.setTextSize(3);      
        displayCenter(String(CulmPwh/1000000),30);
     }
   else if(CulmPwh<1000000 && CulmPwh>1000)
     {
        display.setTextSize(2);
        displayCenter("Energy mWh",3);
        display.setTextSize(3);      
        displayCenter(String(CulmPwh/1000),30);
     }
   else
     {
        display.setTextSize(2);
        displayCenter("Energy uWh",3);
        display.setTextSize(3);      
        displayCenter(String(CulmPwh),30);
     }
  display.display();
  lasttime=millis();
 }

void displayUnits()
{
  display.clearDisplay();
  display.setTextColor(WHITE);
  
    if(units>1000000)
      {
        display.setTextSize(2);
        displayCenter("Units",3); 
        display.setTextSize(3);      
        displayCenter(String(units/1000000),30);
      }
    else if(units<1000000 && units>1000)
      {
        display.setTextSize(2);
        displayCenter("MilliUnits",3);
        display.setTextSize(3);      
        displayCenter(String(units/1000),30);
      }
  else
      {
        display.setTextSize(2);
        displayCenter("MicroUnits",3);
        display.setTextSize(3);      
        displayCenter(String(units),30); 
      }
  display.display();
  lasttime=millis();
 }
 
void CalibCurrent()
{ 
  while(1)
  {
    currentSensor.calibrate();  
    Serial.print("Zero Point Current :");
    Serial.println(currentSensor.getZeroPoint());
    display.clearDisplay();
    display.setTextColor(WHITE);
    display.setTextSize(1);
    display.setCursor(0,0);
    display.print("Current Zero Point :");
    display.setCursor(0,20);
    display.setTextSize(2);
    display.print(currentSensor.getZeroPoint());
    display.display();
    delay(500);
  }
  
}

void CalibVoltage()
{ 
  while(1)
  {
    voltageSensor.calibrate();  
    Serial.print("Zero Point Voltage :");
    Serial.println(voltageSensor.getZeroPoint());
    display.clearDisplay();
    display.setTextColor(WHITE);
    display.setTextSize(1);
    display.setCursor(0,0);
    display.print("Voltage Zero Point :");
    display.setCursor(0,20);
    display.setTextSize(2);
    display.print(voltageSensor.getZeroPoint());
    display.display();
    delay(500);
  }
  
}

void displayCenter(String text, int line) 
{
  int16_t x1;
  int16_t y1;
  uint16_t width;
  uint16_t height;

  display.getTextBounds(text, 0, 0, &x1, &y1, &width, &height);
  // display on horizontal center
  display.setCursor((SCREEN_WIDTH - width) / 2, line);
  display.println(text); // text to display
  display.display();
}

 


About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top