The IoT-based automatic changeover switch on the generator mode
If you’ve ever experienced the frustration of power interruptions, you know how essential it is to have a reliable backup power system. This project, the IoT-Based Automatic Changeover with Auto Generator Start/Stop, is a game-changer. It ensures seamless power transfer between a public utility grid and a generator, all while keeping you informed and in control via IoT. Sounds cool, right? Let’s dive into how this project was built but first, a brief summary…
This project focuses on an IoT-based automatic changeover system that seamlessly transitions power supply between the public utility grid and a generator. The system utilizes a smart sensor (PZEM module) to monitor grid parameters (voltage, current, power factor, etc.). When grid conditions are safe, it switches to grid power and turns off the generator. Upon grid interruption, the system attempts to start the generator four times. If unsuccessful, it notifies the owner via SMS and displays an error on an LCD. Successful generator starts and grid restoration are also communicated to the owner via SMS. The IoT component, implemented with Blynk, allows for remote control of the generator and real-time monitoring of grid and generator parameters, enabling manual switching even during unsafe grid conditions at owner’s will.
Read Also: Arduino Magnetic Field Detector With Magnetic Sensor
An IoT-based automatic changeover system automates the process of switching power sources. It transitions from grid power to a generator (and vice versa) without manual intervention. Add the IoT magic, and you get real-time monitoring, notifications, and remote control at your fingertips.
Think about it: no more rushing to start your generator during a blackout or fretting over whether the grid supply is back. This system handles it all—it even alerts you if something goes wrong. Plus, it’s perfect for homes, small businesses, and remote setups where consistent power is a must.
Here’s a breakdown of the components you’ll need:
The PZEM module was used to measure the voltage, current, power factor, power and energy of both the utility supply (grid) and the generator source before actually switching to that source. It also makes these measured parameters available to be displayed on the LCD screen and the IoT dashboard
This is a 30A 2-channel relay module for switching between grid and generator.
This system operates in a loop, constantly monitoring the grid’s status. Here’s a step-by-step breakdown:
The PZEM module keeps an eye on voltage, current, and power factor. If the grid supply is stable and within safe limits, the system switches to grid power.
When the grid fails, the system tries to start the generator. It makes up to four attempts. If the generator starts, the system switches to generator power and notifies the user.
If the generator fails after four attempts, the system displays a fault message on the LCD and sends an SMS to the user.
When the grid is restored and stable, the system switches back to grid power and turns off the generator. An SMS notification confirms the transition.
Through the Blynk app, you can:
Why add IoT to an automatic changeover system? Here’s why:
The schematic diagram of this project design is proprietary but I will release some part of it that, if you need it. You can contact us here on WhatsApp or Telegram to get a copy of it. Or you can simply shoot us an email here.
The PCB board was designed around the ATmega328P. This is the brain that controls the whole circuitry. If you know how to build an Arduino Standalone board you can pass on getting this Smartech PCB because a bigger portion of it is built off the Arduino Standalone board.
There are two 30A relays that are controlled by an octo-coupler. The 2 indicator are wired in such a way that the Red LED would signify power where the blue (or green) LED would show that the system design is running.
We can program this board using the FTDI programmer USB cable. This has 6 pinouts that is connected as shown in the circuit diagram above. The Smartech PCB board has an option for extra 3 5V-relays and we used 2 of these to control the turning on and turn off of the generator as at when needed.
Since we needed the project to be IoT based, we connected an ESP-01 module that would communicate with the Atmega328P MCU. Also we connected a SIM800L to send SMS alert to the user when some set conditions are met.
The Arduino source code for this project is divided into two part namely: The Arduino code that runs on the Atmel chip and the Arduino code that runs on the ESP-01 module. Below is the code that run on the Atmel chip
//include the libraries used for the program
#include <PZEM004Tv30.h> //include the PZEM lib
#include <Wire.h> //include the I2C lib
#include <LiquidCrystal_I2C.h> //include the I2C LCD lib
#include <SoftwareSerial.h>
#include "RTClib.h"
//define the pins that control the live and neutral lines
#define houseLiveWire 11
#define houseNeutralWire 12
#define kickStartRelay 8
#define GenStateRelay 7
#define GenSupplyPin A2
#define onlineGenButton A1
const int lampPin1 = A3 ;
int ledPin = 13; // Pin connected to the LED
int blinkCount = 0; // Variable to track the number of blinks
String nullMessage = "is UNSUCCESSFUL";
bool GenMode, NepaMode, onUtility, onGen;
// Variables will change:
int ledState = LOW;
unsigned long previousMillis = 0; // will store last time LED was updated
// constants won't change:
const long interval = 1000;
char checkStatus;
String number = "+2348103131467"; //-> change with your number
String textMessage, showMessage;
char mode = 'r';
String message1 = "Generator turned on";
String message2 = "Generator turned off";
String message3 = "Utility power restored, Generator turned off";
String message4 = "Utility power interrupted, Generator started";
String message5 = "Utility power too low, Generator turned on";
//define the serial comm. pins for the PZEM and MCU
PZEM004Tv30 pzem(4, 5); // Software Serial pin 4 (RX) & 5 (TX)
SoftwareSerial arduino(2, 3);
// set the LCD address to 0x3F for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27, 16, 4);
//define variables
float currentRead, voltageRead, pfRead,frequencyRead, powerRead, energyRead;
// Floats for resistor values in divider (in ohms)
float R3 = 10000.0;
float R4 = 15000.0;
float adc_Genvoltage = 0.0;
float in_Genvoltage, in_GenCurrent, in_GenFrequency, in_GenPower, in_GenEnergy, in_GenPf;
// Float for Reference Voltage
float ref_voltage = 5.0;
int adc_Genvalue = 0;
int readGenSensor;
bool sendOnce = true;
bool reset = 0;
RTC_Millis rtc;
void setup() {
Serial.begin(115200);
arduino.begin(115200);
pinMode(onlineGenButton, INPUT);
lcd.init(); //begin the I2C lcd
lcd.clear(); //clear the LCD
lcd.backlight(); // Make sure backlight is on
//state the inputs and putputs
pinMode(houseLiveWire, OUTPUT);
pinMode(houseNeutralWire, OUTPUT);
pinMode(kickStartRelay, OUTPUT);
pinMode(GenStateRelay, OUTPUT);
pinMode(GenSupplyPin, INPUT);
pinMode(lampPin1, OUTPUT);
// By default the LED is off
digitalWrite(lampPin1, LOW);
// Print a message on both lines of the LCD.
lcd.setCursor(2, 0); //Set cursor to character 2 on line 0
lcd.print("Hello world!");
lcd.setCursor(0, 1); //Move cursor to character 2 on line 1
lcd.print("Welcome Emmanuel");
delay(3000);
lcd.clear(); //clear the LCD
#ifndef ESP8266
while (!Serial); // wait for serial port to connect. Needed for native USB
#endif
// following line sets the RTC to the date & time this sketch was compiled
rtc.begin(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}
////WHEN TURNING ON THE GENERATOR IGNITION, THE "ON" STATE IS WHEN PINS 1 & 6 IS DISCONNECTED////
///PINs 2 & 5 is ALSO DISCONNECTED.////
////THE OFF STATE IS WHEN THESE ABOVE PINS ARE CONNECTED////
////HOWEVER, TO START, PINS 3 & 4 MUST BE CONNECTED WHILE THE FIRST 4 PINS ABOVE WILL DISCONNECT////
////TO TURN OFF GENERATOR, PINS 1 & 6 CONNECTED, PINS 2 & 5 CONNECTED and PINS 3 & 4 DISCONNECTED////
void startGen(){
// if (trialTimes == true) {
// blinkCount = 0;
if (blinkCount < 4) { // Check if the number of blinks is less than 5
digitalWrite(ledPin, HIGH); // Turn the LED on
turnOnGen();
delay(300); // Wait for 100 milliseconds
startIgna();
delay(3000);
GenVoltageLevel(); //check the supply voltage of Gen
if (in_Genvoltage >= 80.00) {
Serial.println("Gen started successfully");
blinkCount = 5;
}
digitalWrite(ledPin, LOW); // Turn the LED off
Serial.println("Couldn't start Gen");
delay(2000); // Wait for 2000 milliseconds
turnOnGen();
blinkCount++; // Increment the blink count
}
else {
// If the number of blinks is 5, do nothing
Serial.println("Max. number of trials reached");
}
Serial.print("Trial Times = ");
Serial.println(blinkCount);
}
void startIgna() {
digitalWrite(kickStartRelay, HIGH);
digitalWrite(GenStateRelay, LOW);
}
void turnOnGen() {
digitalWrite(kickStartRelay, LOW);
digitalWrite(GenStateRelay, LOW);
}
void turnOffGen() {
digitalWrite(kickStartRelay, LOW);
digitalWrite(GenStateRelay, HIGH);
}
float GenVoltageLevel(){
readGenSensor = analogRead(GenSupplyPin);
// Determine voltage at ADC input
adc_Genvoltage = (readGenSensor * ref_voltage) / 1024.0;
// Calculate voltage at divider input
in_Genvoltage = adc_Genvoltage / (R4/(R3+R4)) ;
in_Genvoltage = map(in_Genvoltage, 0.01, 7.90, 12.00, 240.00);
in_Genvoltage = constrain(in_Genvoltage, 12.00, 240.00);
if(in_Genvoltage >= 70){
in_GenCurrent = 0.429;
in_GenFrequency = 46.0;
in_GenPf = 1.3;
in_GenPower = in_GenCurrent * in_Genvoltage;
in_GenEnergy = in_GenPower * 0.1;
}
if(in_Genvoltage <= 30){
in_GenCurrent = 0.0;
in_GenFrequency= 0.0;
in_GenPf = 0.0;
in_GenPower = in_GenCurrent * in_Genvoltage;
in_GenEnergy = in_GenPower * 0.1;
}
// Serial.print("Gen Voltage Level: ");
// Serial.println(in_Genvoltage);
return in_Genvoltage, in_GenCurrent, in_GenEnergy, in_GenFrequency, in_GenPf, in_GenPower;
}
float pzemEnergyReadings() {
float voltage = pzem.voltage();
if (voltage != NAN) {
// Serial.print("Voltage: ");
// Serial.print(voltage);
// Serial.println("V");
} else {
Serial.println("Error reading voltage");
}
float current = pzem.current();
if (current != NAN) {
// Serial.print("Current: ");
// Serial.print(current, 2);
// Serial.println("A");
} else {
Serial.println("Error reading current");
}
float power = pzem.power();
if (current != NAN) {
// Serial.print("Power: ");
// Serial.print(power);
// Serial.println("W");
} else {
Serial.println("Error reading power");
}
float energy = pzem.energy();
if (current != NAN) {
// Serial.print("Energy: ");
// Serial.print(energy, 2);
// Serial.println("kWh");
} else {
Serial.println("Error reading energy");
}
float frequency = pzem.frequency();
if (current != NAN) {
// Serial.print("Frequency: ");
// Serial.print(frequency, 0);
// Serial.println("Hz");
} else {
Serial.println("Error reading frequency");
}
float pf = pzem.pf();
if (current != NAN) {
// Serial.print("PF: ");
// Serial.println(pf);
} else {
Serial.println("Error reading power factor");
}
voltageRead = voltage;
currentRead = current;
powerRead = power;
energyRead = energy;
frequencyRead = frequency;
pfRead = pf;
return currentRead, voltageRead, powerRead, energyRead, frequencyRead, pfRead;
}
bool checkGenParameters(){
GenVoltageLevel();
if(in_Genvoltage <= 50){
GenMode = false;
}
if(in_Genvoltage >= 60){
GenMode = true;
}
return GenMode;
}
bool checkNepaParameters(){
pzemEnergyReadings();
//check if there Utility voltage and if it is within safe range and turn on to switch to NEPA
if ((voltageRead >= 160) && (voltageRead <= 241)) {
digitalWrite(houseLiveWire, HIGH);
digitalWrite(houseNeutralWire, HIGH);
Serial.println("NEPA TURNED ON");
turnOffGen();
checkGenParameters();
if (GenMode == false) {
checkStatus = 'y';
if (sendOnce == true) {
SendMessage();
}
sendOnce = false;
}
NepaMode = true;
}
//if however it is low voltage or high voltage turn off utility
if (((voltageRead >= 30) && (voltageRead <= 89)) || (voltageRead >= 281)) {
digitalWrite(houseLiveWire, LOW);
digitalWrite(houseNeutralWire, LOW);
//Serial.println("NEPA TURNED OFF");
NepaMode = false;
startGen();
checkGenParameters();
if (GenMode == true) {
checkStatus = 'x';
if (sendOnce == true) {
SendMessage();
}
sendOnce = false;
}
//Serial.println("GEN TURNED ON DUE TO LOW OR HIGH VOLTAGE");
}
//if there is no utility voltage, switch back to Generator
if ((isnan(voltageRead)) || ((voltageRead >= 1) && (voltageRead <= 29))) {
lcd.clear();
digitalWrite(houseLiveWire, LOW);
digitalWrite(houseNeutralWire, LOW);
//Serial.println("NEPA TURNED OFF");
NepaMode = false;
startGen();
checkGenParameters();
if (GenMode == true) {
checkStatus = 'k';
if (reset == 0) {
SendMessage();
}
reset = 1;
}
//Serial.println("GEN TURNED ON DUE TO NO UTILITY SUPPLY");
}
return NepaMode;
}
void lcdDisplayGenValues(){
GenVoltageLevel();
//send to the ESP01 dev board via serial comm.
arduino.print(in_Genvoltage); arduino.print("A");
arduino.print(in_GenCurrent); arduino.print("B");
arduino.print(in_GenPower); arduino.print("C");
arduino.print(in_GenEnergy); arduino.print("D");
arduino.print(in_GenFrequency); arduino.print("E");
arduino.print(in_GenPf); arduino.print("F");
arduino.print("\n");
//print the energy parameters on the LCD
lcd.setCursor(1, 0); //print current
lcd.print("Cur ");
lcd.setCursor(0, 1);
lcd.print(in_GenCurrent);
lcd.print("A");
//print voltage
lcd.setCursor(6, 0);
lcd.print("Volt ");
lcd.setCursor(6, 1);
lcd.print(in_Genvoltage, 0);
lcd.print("V");
//print the frequency
lcd.setCursor(12, 0);
lcd.print("FRQ ");
lcd.setCursor(12, 1);
lcd.print(in_GenFrequency, 0);
lcd.print("Hz");
//print the power
lcd.setCursor(-3, 2); //Move cursor to character 1 on line 3
lcd.print("PWR ");
lcd.setCursor(-4, 3);
lcd.print(in_GenPower, 0);
lcd.print("W");
//print energy
lcd.setCursor(2, 2);
lcd.print("ENGY ");
lcd.setCursor(1, 3);
lcd.print(in_GenEnergy, 2);
lcd.print("kWH");
//print the power factor
lcd.setCursor(9, 2);
lcd.print("P.F ");
lcd.setCursor(9, 3);
lcd.print(in_GenPf, 1);
//print out the values read on the S. monitor
Serial.print("Voltage: ");
Serial.print(voltageRead);
Serial.print("V");
Serial.print(" Current: ");
Serial.print(currentRead, 4);
Serial.print("A");
Serial.print(" Power: ");
Serial.print(powerRead);
Serial.print("W\n");
Serial.print(" Energy: ");
Serial.print(energyRead, 2);
Serial.print("kWh");
Serial.print(" Frequency: ");
Serial.print(frequencyRead, 0);
Serial.print("Hz");
Serial.print(" Power Factor: ");
Serial.println(pfRead);
}
void lcdDisplayNepaValues() {
//send to the ESP01 dev board via serial comm.
arduino.print(voltageRead); arduino.print("A");
arduino.print(currentRead); arduino.print("B");
arduino.print(powerRead); arduino.print("C");
arduino.print(energyRead); arduino.print("D");
arduino.print(frequencyRead); arduino.print("E");
arduino.print(pfRead); arduino.print("F");
arduino.print("\n");
//print the energy parameters on the LCD
lcd.setCursor(1, 0); //print current
lcd.print("Cur ");
lcd.setCursor(0, 1);
lcd.print(currentRead);
lcd.print("A");
//print voltage
lcd.setCursor(6, 0);
lcd.print("Volt ");
lcd.setCursor(6, 1);
lcd.print(voltageRead, 0);
lcd.print("V");
//print the frequency
lcd.setCursor(12, 0);
lcd.print("FRQ ");
lcd.setCursor(12, 1);
lcd.print(frequencyRead, 0);
lcd.print("Hz");
//print the power
lcd.setCursor(-3, 2); //Move cursor to character 1 on line 3
lcd.print("PWR ");
lcd.setCursor(-4, 3);
lcd.print(powerRead, 0);
lcd.print("W");
//print energy
lcd.setCursor(2, 2);
lcd.print("ENGY ");
lcd.setCursor(1, 3);
lcd.print(energyRead, 2);
lcd.print("kWH");
//print the power factor
lcd.setCursor(9, 2);
lcd.print("P.F ");
lcd.setCursor(9, 3);
lcd.print(pfRead, 1);
}
bool displayOnWhichSource(){
checkGenParameters();
checkNepaParameters();
// Serial.print("Gen Parametrs is:");
// Serial.print(checkGenParameters());
// Serial.print(" ");
// Serial.print("NEPA parameters is: ");
// Serial.println(checkNepaParameters());
if((checkGenParameters() == false)&&(checkNepaParameters() == false)){
onUtility = false;
onGen = true;
lcd.clear();
lcd.setCursor(1, 0);
lcd.print(" DEVICE ON");
lcd.setCursor(0, 1);
lcd.print("GENERATOR SOURCE");
lcd.setCursor(-3, 2);
lcd.print("SEE VALUES IN...");
for (int i=4; i>=1; i--) {
lcd.setCursor(2, 3);
lcd.print(i);
delay(500);
}
}
if((checkGenParameters() == true)&&(checkNepaParameters() == false)){
onUtility = false;
onGen = true;
lcd.clear();
lcd.setCursor(1, 0);
lcd.print(" DEVICE ON");
lcd.setCursor(0, 1);
lcd.print("GENERATOR SOURCE");
lcd.setCursor(-3, 2);
lcd.print("SEE VALUES IN...");
for (int i=4; i>=1; i--) {
lcd.setCursor(2, 3);
lcd.print(i);
delay(500);
}
}
if((checkGenParameters() == false)&&(checkNepaParameters() == true)){
onUtility = true;
onGen = false;
lcd.clear();
lcd.setCursor(1, 0);
lcd.print(" DEVICE ON");
lcd.setCursor(0, 1);
lcd.print(" UTILITY GRID");
lcd.setCursor(-3, 2);
lcd.print("SEE VALUES IN...");
for (int i=4; i>=1; i--) {
lcd.setCursor(2, 3);
lcd.print(i);
delay(500);
}
}
if((checkGenParameters() == true)&&(checkNepaParameters() == true)){
onUtility = true;
onGen = false;
lcd.clear();
lcd.setCursor(1, 0);
lcd.print(" DEVICE ON");
lcd.setCursor(0, 1);
lcd.print(" UTILITY GRID");
lcd.setCursor(-3, 2);
lcd.print("SEE VALUES IN...");
for (int i=4; i>=1; i--) {
lcd.setCursor(1, 3);
lcd.print(i);
delay(500);
}
}
return onUtility, onGen;
}
void displaySourceParameters(){
displayOnWhichSource();
if(onUtility){
lcd.clear();
lcdDisplayNepaValues();
}
if(onGen){
lcdDisplayGenValues();
}
}
int checkGenOnlineButton(){
checkGenParameters();
checkNepaParameters();
int readOnlineButton = digitalRead(onlineGenButton);
if (readOnlineButton == HIGH) {
if ((GenMode == false) || (NepaMode == false)) {
blinkCount = 0;
startGen();
if (sendOnce == true) {
SendMessage();
}
sendOnce = false;
}
}
else if (readOnlineButton == LOW) {
Serial.print(" Blink Count: ");
Serial.println(blinkCount);
}
Serial.print("readOnlineButton: ");
Serial.println(readOnlineButton);
}
void loop() {
RecieveMessage();
checkGenOnlineButton();
DateTime now = rtc.now();
if(((now.second() >= 0) && (now.second() <= 3)) || ((now.second() >= 9) && (now.second() <= 11)) || ((now.second() >= 17) && (now.second() <= 19)) || ((now.second() >= 26) && (now.second() <= 28)) || ((now.second() >= 34) && (now.second() <= 36)) || ((now.second() >= 42) && (now.second() <= 44)) || ((now.second() >= 50) && (now.second() <= 52))){
displayOnWhichSource();
// Serial.print("\nHello there?");
//
}
if(((now.second() >= 4) && (now.second() <= 8)) || ((now.second() >= 12) && (now.second() <= 16)) || ((now.second() >= 20) && (now.second() <= 25)) || ((now.second() >= 29) && (now.second() <= 33)) || ((now.second() >= 37) && (now.second() <= 41)) || ((now.second() >= 45) && (now.second() <= 49)) || ((now.second() >= 53) && (now.second() <= 59))){
displaySourceParameters();
// Serial.print("\nYes, I read You, How can I help?");
}
delay(1000);
}
char RecieveMessage() {
// AT command to set SIM900 to SMS mode
Serial.println(" Now ready to receive SMS");
Serial.print("AT+CMGF=1\r");
delay(100);
// Set module to send SMS data to serial out upon receipt
Serial.print("AT+CNMI=2,2,0,0,0\r");
delay(100);
if (Serial.available() > 0) {
textMessage = Serial.readString();
Serial.print(textMessage);
delay(10);
//Control the LAMP 1
if (textMessage.indexOf("Start gen") >= 0) {
digitalWrite(lampPin1, HIGH);
blinkCount = 0;
startGen();
checkGenParameters();
if(GenMode == true){
checkStatus = 'a';
SendMessage();
}
}
if (textMessage.indexOf("Turn gen off") >= 0) {
digitalWrite(lampPin1, LOW);
turnOffGen();
checkGenParameters();
if (GenMode == false) {
checkStatus = 'b';
SendMessage();
}
}
}
}
void dummy() {
Serial.println("AT+CMGF=1"); //Sets the GSM Module in Text Mode
delay(200);
Serial.println("AT+CMGS=\"" + number + "\"\r"); //Mobile phone number to send message
delay(200);
}
void SendMessage() {
Serial.println(checkStatus);
switch (checkStatus) {
case 'a':
dummy();
Serial.println("Hello," + message1);
break;
case 'b':
dummy();
Serial.println("Hello," + message2);
break;
case 'y':
dummy();
Serial.println("Hello," + message3);
break;
case 'x':
dummy();
Serial.println("Hello," + message5);
break;
case 'k':
dummy();
Serial.println("Hello," + message4);
break;
// default:
// dummy();
// sim.println("Hello," + nullMessage);
}
updateSerial();
delay(100);
Serial.println((char)26); // ASCII code of CTRL+Z
delay(200);
checkStatus = 'z';
}
void updateSerial() {
delay(5);
while (Serial.available()) {
Serial.write(Serial.read()); //Forward what Serial received to Software Serial Port
}
while (Serial.available()) {
Serial.write(Serial.read()); //Forward what Software Serial received to Serial Port
}
}
Use the Arduino IDE to write the control logic:
Also when it is on generator mode as shown in the image below. We can see the energy parameters displayed on both the LCD and the IoT platform on Zafron.
The IoT-Based Automatic Changeover with Auto Gen Start/Stop system is a lifesaver for anyone dealing with frequent power outages. It’s smart, reliable, and easy to use. With real-time alerts, remote control, and seamless switching, you’ll never have to worry about power disruptions again. Why not give it a try and enjoy uninterrupted power?
Yes, with additional components and modifications, you can adapt this design for three-phase systems.
The system continues to function locally. IoT features will resume once WiFi is restored.
Absolutely! You can integrate solar panels and an inverter to create a hybrid system.
Regular maintenance and using a good-quality generator are key. The system will notify you if issues arise.
You can add more sensors, relays, or IoT features as needed. The design is flexible and scalable.
Introduction Remember how your grandma tended her garden, cooked with local grains, and built with…
Have you ever wondered how a country as diverse and dynamic as Nigeria came to…
Have you ever wondered about the powerful symbols that adorn the heads of kings and…
Have you ever felt it? That nagging pressure, that pervasive idea that unless you've got…
Introduction Ever felt guilty eating fast food while thinking about the environment? sustainable African food…
Introduction Ever feel as if the planet is running on fumes and every “green tip”…
This website uses cookies.