const char auth[] = "ir9wtss6cDo4rGWZAnKUfJbgBKpgNkcC";
const char ssid[] = "Wokwi_GUEST";
const char pass[] = "";
#define BLYNK_TEMPLATE_ID "TMPL6T70krTot"
#define BLYNK_TEMPLATE_NAME "IoT Energy Meter"
//#define BLYNK_AUTH_TOKEN "ir9wtss6cDo4rGWZAnKUfJbgBKpgNkcC"
#define BLYNK_PRINT Serial
#include <EEPROM.h> // Include Libraries
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <Wire.h>
#include <ACS712.h>
#include "EmonLib.h"
#include <LiquidCrystal_I2C.h>
//-----------------------------------------------Pins---------------------------------------------------------------------------//
#define SOL_ADC 2 // Solar panel side voltage divider is connected to pin 2
#define BAT_ADC 4 // Battery side voltage divider is connected to pin 4
#define LOAD_CURRENT_ADC 19 // ACS 712 current sensor is connected to pin 19 for load curremt
#define SOL_CURRENT_ADC 21 // ACS 712 current sensor is connected to pin 21 for solar current
#define AVG_NUM 10 // number of iterations of the adc routine to average the adc readings
// Variables for energy calculation
unsigned long lastMillis = millis();
const float vCalibration = 41.5; // Constants for calibration
const float currCalibration = 0.15;
const int ledPin = 13; // Set LED and physical button pins here
const int btnPin = 12;
int up = 15; //Up button
int down = 18; //Down button
LiquidCrystal_I2C lcd(0x27, 20, 4);
//-----------------------------------------Counter to change positions of pages---------------------------------------//
int page_counter = 1 ; //To move beetwen pages
unsigned long previousMillis = 0; //Variables for autorreturn
unsigned long interval = 100; //Desired wait time
float solar_volt = 0;
float bat_volt = 0;
float load_current = 0;
float solar_current = 0;
float offsetVoltage = 2.5; // for ACS712 sensor
float Sensitivity = 0.66; // 185mV/A for ACS712-5A variant,66mV/A for 30A
float last_time = 0;
float current_time = 0;
//long unsigned time = 0;
long unsigned msec = 0;
long unsigned last_msec = 0;
long unsigned elasped_msec = 0;
long unsigned elasped_time = 0;
float load_ampSecs = 0;
float load_ampHours = 0;
float load_watts = 0;
float load_wattSecs = 0;
float load_wattHours = 0;
float solar_ampSecs = 0;
float solar_ampHours = 0;
float solar_watts = 0;
float solar_wattSecs = 0;
float solar_wattHours = 0;
const int addrVrms = 0; // EEPROM addresses for each variable
const int addrIrms = 4;
const int addrPower = 8;
const int addrKWh = 12;
float kWh = 0.0;
//----------------------------------------------------Storage debounce function-------------------------------------------------------//
boolean current_up = LOW;
boolean last_up = LOW;
boolean last_down = LOW;
boolean current_down = LOW;
EnergyMonitor emon1; //Create an instance
BlynkTimer timer;
void checkPhysicalButton();
int ledState = LOW;
int btnState = HIGH;
// Every time we connect to the cloud...
BLYNK_CONNECTED() {
// Request the latest state from the server
//Blynk.syncVirtual(V5);
// Alternatively, you could override server state using:
//Blynk.virtualWrite(V2, ledState);
Blynk.virtualWrite(V0, emon1.Vrms);
Blynk.virtualWrite(V1, emon1.Irms);
Blynk.virtualWrite(V2, emon1.apparentPower);
Blynk.virtualWrite(V3, kWh);
}
// When App button is pushed - switch the state
BLYNK_WRITE(V4) {
ledState = param.asInt();
digitalWrite(ledPin, ledState);
}
void checkPhysicalButton()
{
if (digitalRead(btnPin) == LOW) {
// btnState is used to avoid sequential toggles
if (btnState != LOW) {
// Toggle LED state
ledState = !ledState;
digitalWrite(ledPin, ledState);
Blynk.virtualWrite(V0, emon1.Vrms);
Blynk.virtualWrite(V4, emon1.Irms);
Blynk.virtualWrite(V2, emon1.apparentPower);
Blynk.virtualWrite(V3, kWh);
// Update Button Widget
//Blynk.virtualWrite(V5, ledState);
//Blynk.virtualWrite(V2, ledState);
}
btnState = LOW;
} else {
btnState = HIGH;
}
}
// Function prototypes
void sendEnergyDataToBlynk();
void readEnergyDataFromEEPROM();
void saveEnergyDataToEEPROM();
void setup()
{
Serial.begin(9600);
//emon1.voltage(34, 0.5, 1.7); // Voltage: input pin, calibration, phase_shift
//emon1.current(35, 1.1); // Current: input pin, calibration.
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print(" IoT METER ");
lcd.setCursor(0, 1 );
lcd.print(" INITIALISE ");
lcd.setCursor(0, 2 );
lcd.print(" SUCCESS!!! ");
lcd.setCursor(0, 3 );
lcd.print("By KAMALISUBASRI R ");
delay(3000);
//lcd.clear();
Blynk.begin(auth, ssid, pass);
pinMode(ledPin, OUTPUT);
pinMode(btnPin, INPUT_PULLUP);
digitalWrite(ledPin, ledState);
// Initialize EEPROM with the size of the data to be stored
EEPROM.begin(32); // Allocate 32 bytes for float values (4 bytes each) and some extra space
// Read the stored energy data from EEPROM
readEnergyDataFromEEPROM();
emon1.voltage(35, vCalibration, 1.7); // Voltage: input pin, calibration, phase_shift
emon1.current(34, currCalibration); // Current: input pin, calibration
timer.setInterval(5000L, sendEnergyDataToBlynk);
timer.setInterval(100L, checkPhysicalButton);
// A small delay for system to stabilize
delay(1000);
}
//---------------------------------------------------- De-bouncing function for all buttons----------------------------------------//
boolean debounce(boolean last, int pin)
{
boolean current = digitalRead(pin);
if (last != current)
{
//delay(5);
current = digitalRead(pin);
}
return current;
}
void loop()
{
Blynk.run();
timer.run();
read_data(); // read different sensors data from analog pin of esp32
emon1.calcVI(20,1000); // Calculate all. No.of half wavelengths (crossings), time-out
emon1.serialprint(); // Print out all variables (realpower, apparent power, Vrms, Irms, power factor)
// Calculate energy consumed in kWh
unsigned long currentMillis = millis();
kWh += emon1.apparentPower * (currentMillis - lastMillis) / 3600000000.0;
lastMillis = currentMillis;
float realPower = emon1.realPower; //extract Real Power into variable
float apparentPower = emon1.apparentPower; //extract Apparent Power into variable
float powerFActor = emon1.powerFactor; //extract Power Factor into Variable
float supplyVoltage = emon1.Vrms; //extract Vrms into Variable
float Irms = emon1.Irms; //extract Irms into Variable
Serial.printf("Vrms: %.2fV\tIrms: %.4fA\tPower: %.4fW\tkWh: %.5fkWh\n", // Print data to Serial for debugging
emon1.Vrms, emon1.Irms, emon1.apparentPower, kWh);
Blynk.virtualWrite(V0, emon1.Vrms);
Blynk.virtualWrite(V4, emon1.Irms);
Blynk.virtualWrite(V2, emon1.apparentPower);
Blynk.virtualWrite(V3, kWh);
lcd.clear();
current_up = debounce(last_up, up); //Debounce for Up button
current_down = debounce(last_down, down); //Debounce for Down button
//-----------------------------------------------------Page counter function to move pages-----------------------------------------------------------------//
if (last_up== LOW && current_up == HIGH){ //Page Up
if(page_counter <4){ //Page counter never higher than 3(total of pages)
page_counter= page_counter +1; //Page up
lcd.clear(); //When page is changed, lcd clear to print new page
}
else{
page_counter= 4;
}
}
last_up = current_up;
//Page Down
if (last_down== LOW && current_down == HIGH){
if(page_counter >1){ //Page counter never lower than 1 (total of pages)
page_counter= page_counter -1; //Page down
lcd.clear(); //When page is changed, lcd clear to print new page
}
else{
page_counter= 1;
}
}
last_down = current_down;
//------------------------------------------------------------- Switch function to diplay on LCD----------------------------------------------//
/*switch (page_counter) { //Display Energy from inverter to utilities
case 1:{
lcd.setCursor(0, 0);
lcd.print("Vrms: ");
lcd.print(emon1.Vrms, 3);
lcd.print(" V");
lcd.setCursor(0, 1);
lcd.print("Irms: ");
lcd.print(emon1.Irms, 3);
lcd.print(" A");
lcd.setCursor(0, 2);
lcd.print("Pwat: ");
lcd.print(emon1.apparentPower, 3);
lcd.print(" W");
lcd.setCursor(0, 3);
lcd.print("kWh : ");
lcd.print(kWh, 4);
lcd.print(" kWh");
}
break;
case 2: { //display Solar parameter
lcd.setCursor(0, 0);
lcd.print(" SOLAR PARAMETER");
lcd.setCursor(0, 1);
lcd.print("SOL Volts ");
lcd.print( solar_volt, 1);
lcd.print(" V");
lcd.setCursor(0, 2);
lcd.print("SOL Amps ");
lcd.print( solar_current, 1);
lcd.print(" A");
lcd.setCursor(0,3);
lcd.print("SOL Watts ");
lcd.print( solar_watts, 1 );
lcd.print(" W ");
}
break;
case 3: { // Display Load Parameters
lcd.setCursor(0, 0);
lcd.print(" LOAD PARAMETER");
lcd.setCursor(0,1);
lcd.print("LOAD Amps ");
lcd.print(load_current,1);
lcd.print("A");
lcd.setCursor(0,2);
lcd.print("LOAD Watts ");
lcd.print(load_watts,1);
lcd.print("W");
}
break;
case 4: { // Display Energy from solar
lcd.setCursor(0, 0);
lcd.print("ENERGY PARAMETER");
lcd.setCursor(0,1);
lcd.print("SOL Wh ");
lcd.print(solar_wattHours,1);
lcd.print("Wh");
lcd.setCursor(0,2);
lcd.print("LOAD Wh ");
lcd.print(load_wattHours,1);
lcd.print("Wh");
}
break;
} //switch end
*/
//--------------------------------------------------------Auto return function---------------------------------------------------------------------//
//unsigned long currentMillis = millis();
//lcd.setCursor(0,1); // Show millis counter status
//lcd.print((currentMillis-previousMillis)/1000);
/* if (currentMillis - previousMillis > interval) { //If interval is reached, return to home page 1
previousMillis = currentMillis;
//lcd.clear();
page_counter = 1;
}
if (digitalRead(15) == HIGH || digitalRead(18)==HIGH){ // Reset millis counter If any button is pressed
previousMillis = currentMillis;
}
*/
}
//-------------------------------------------------------------------------read analog voltages and current from sensors---------------------------------------------------------//
int read_adc(int adc_parameter)
{
int sum = 0;
int sample ;
for (int i=0; i<AVG_NUM; i++)
{ // loop through reading raw adc values AVG_NUM number of times
sample = analogRead(adc_parameter); // read the input pin
sum += sample; // store sum for averaging
delayMicroseconds(50); // pauses for 50 microseconds
}
return(sum / AVG_NUM); // divide sum by AVG_NUM to get average and return it
}
//-------------------------------------------------------------------------------------------------------------
////////////////////////////////////READ THE DATA//////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------------------
void read_data(void) //5V = ADC value 1024 => 1 ADC value = (5/1024)Volt= 0.0048828Volt
{ // Vout=Vin*R2/(R1+R2) => Vin = Vout*(R1+R2)/R2 R1=100 and R2=20
solar_volt = read_adc(SOL_ADC)*0.00488*(120/20);
bat_volt = read_adc(BAT_ADC)*0.00488*(120/20);
load_current = ((read_adc(LOAD_CURRENT_ADC)*0.00488 - offsetVoltage-2.5)/Sensitivity ); // 2.4V offset when no load is connected
solar_current = ((read_adc(SOL_CURRENT_ADC)*0.00488 - offsetVoltage)/ Sensitivity );
if (load_current <0)
{
load_current = 0;
}
if (solar_current <0)
{
solar_current = 0;
}
}
//------------------------------------------------------------------------------------------------------------
/////////////////////////////////POWER AND ENERGY CALCULATION //////////////////////////////////////////////
//------------------------------------------------------------------------------------------------------------
void power(void){
msec = millis();
elasped_msec = msec - last_msec; //Calculate how long has past since last call of this function
elasped_time = elasped_msec / 1000.0; // 1sec=1000 msec
load_watts = load_current * bat_volt; //Watts now
solar_watts = solar_current * solar_volt; //Watts now
load_ampSecs = (load_current*elasped_time); //AmpSecs since last measurement
solar_ampSecs = (solar_current*elasped_time); //AmpSecs since last measurement
load_wattSecs = load_ampSecs * bat_volt; //WattSecs since last measurement
solar_wattSecs = solar_ampSecs * solar_volt; //WattSecs since last measurement
load_ampHours = load_ampHours + load_ampSecs/3600; // 1 hour=3600sec //Total ampHours since program started
solar_ampHours = solar_ampHours + solar_ampSecs/3600; // 1 hour=3600sec //Total ampHours since program started
load_wattHours = load_wattHours + load_wattSecs/3600; // 1 hour=3600sec //Total wattHours since program started
solar_wattHours = solar_wattHours + solar_wattSecs/3600; // 1 hour=3600sec //Total wattHours since program started
last_msec = msec; //Store 'now' for next time
last_time = current_time;
current_time = millis();
load_watts = load_current * bat_volt; //load Watts now
solar_watts = solar_current * solar_volt; //solar Watts now
load_wattHours = load_wattHours + load_watts*(( current_time -last_time) /3600000.0) ; // calculating energy in Watt-Hour
solar_wattHours = solar_wattHours+ solar_watts*(( current_time -last_time) /3600000.0) ; // calculating energy in Watt-Hour
}