// =======================================================
// Automatic reserve gasoline generator start controller
// © OBO email: [email protected]
// ver. 0.0
// =======================================================
//
// This program is designed for the automatic starting of a backup gasoline generator, requiring control of the choke mechanism.
// The status of the main city power is monitored via the CityVPin.
// While the generator is running, the engine’s oil pressure (OilPin) is continuously monitored.
// Three indicator LEDs and an LCD screen are used to display the generator's current status:
// - Green LED (Solid): the generator is operating normally.
// - Green LED (Blinking): the generator has automatically paused after reaching its maximum permitted run time.
// - Yellow LED (Solid): the generator is in standby mode, ready to start.
// - Red LED (Solid): the generator has shut down due to an error (low oil, overheating, fuel fault, or power loss). Press the Manual Button to reset the system.
// - Red LED (Blinking): the ambient temperature is outside the specified operating range.
// A single button is used for manual starting and stopping of the generator, as well as for clearing error states.
// The system can accept an input from an external time relay (SchedulePin) to initiate a scheduled maintenance run. The duration for this run is preconfigured.
// The system calculates the total runtime of the generator. This time is displayed on the LCD and saved to the EEPROM.
// A maximum continuous operation time is preset. If the generator runs longer than this setting, it will be automatically stopped and then restarted after a preset cool-down (pause) period.
const byte verMajor = 0; //program version major
const byte verMinor = 0; //program version minor
#include <EEPROM.h> //include external library for EEPROM access
#include <LiquidCrystal_I2C.h> //include external library for LCD
LiquidCrystal_I2C lcd(0x27, 16, 2); //initilize LCD screen (16 chars x 2 rows)
#define GreenLed 11 //successful run LED indicator
#define RedLed 12 //error led indicator
#define YellowLed 13 //standby led indicator
#define OilPin A3 //oil pressure sensor
#define CityVPin A2 //city (mains) energy sensor
#define GenVPin A1 //generator energy sensor
#define GenRunPin A0 //generator run sensor (e.g. 12V outlet)
#define ChokeOnPin 10 //choke close actuator relay
#define ChokeOffPin 9 //choke open actuator relay
#define IgnitionPin 8 //generator ignition control
#define StarterPin 7 //generator starter relay
#define PowerPin 6 //generator load switch relay
#define ButtonPin 5 //button: start/stop, reset
#define SchedulePin 4 //external time relay for periodic service runs
const byte startOnTime = 3; //starter activation duration, seconds (0...255)
const byte startPause = 10; //pause between start attempts, seconds (0...255)
const byte warmCoolTime = 30; //warm-up/cool-down time (run generator without load), seconds (0...255)
const unsigned int chokeActTime = 1000; //choke actuator activation time (ms)
const unsigned int workTimePermit = 240; //maximum permitted generator runtime (sec)
const unsigned int idleTime = 120; //idle pause duration (sec)
const unsigned int workTimeSchedule = 90; //scheduled run duration (runtime + cooling time), seconds
byte genStartCount = 0; //successful generator start counter
unsigned long runTime = 0; //current generator runtime
unsigned long totalRunTime = 0; //total generator runtime
unsigned long startTime = 0; //time when generator started
unsigned long stopTime = 0; //time when generator stopped
unsigned long coolingStartTime = 0; //time when load switched OFF, alternator cooling started
unsigned long previousMillis = 0; //variable for non-blocking delays using millis()
unsigned long lastDebounceTime = 0; //last time the pin toggled
unsigned long debounceDelay = 50; //debounce time in ms
bool timerStop = false;
bool scheduleStop = false;
bool genError = false;
bool genIsRunning = false;
bool genIsCoolingDown = false;
bool genPower = false;
bool runBySchedule = false;
bool runByButton = false;
bool stopByButton = false;
bool chokeClose = false;
byte ignitionState = LOW;
byte ledState = LOW;
byte buttonState = HIGH;
byte lastButtonState = HIGH;
void setup() {
lcd.begin(16, 2); //startup lcd library
lcd.init();
lcd.backlight();
pinMode(GenRunPin, INPUT_PULLUP); //generator run sensor
pinMode(CityVPin, INPUT_PULLUP); //city power sensor
pinMode(GenVPin, INPUT_PULLUP); //generator power sensor
pinMode(ButtonPin, INPUT_PULLUP); //button
pinMode(OilPin, INPUT_PULLUP); //oil sensor
pinMode(ChokeOnPin, OUTPUT); //choke close
pinMode(ChokeOffPin, OUTPUT); //choke open
pinMode(IgnitionPin, OUTPUT); //ignition switch
pinMode(StarterPin, OUTPUT); //starter switch
pinMode(PowerPin, OUTPUT); //generator power switch
pinMode(GreenLed, OUTPUT);
pinMode(YellowLed, OUTPUT);
pinMode(RedLed, OUTPUT);
//initial set relays OFF
digitalWrite(ChokeOnPin, LOW);
digitalWrite(ChokeOffPin, LOW);
digitalWrite(IgnitionPin, LOW);
digitalWrite(StarterPin, LOW);
digitalWrite(PowerPin, LOW);
//write initial 0 value for total work time to EEPROM if it's the first run
if (EEPROM.read(0) == 255 && EEPROM.read(1) == 255) {
writeULong(0, totalRunTime);
}
//welcome lcd screen
verlcdprint(verMajor, verMinor);
}
void loop() {
//generator error handling
while (genError == true) {
//stop on error
if (genError == true) {
digitalWrite(PowerPin, LOW); //generator power OFF
ignitionState = LOW;
digitalWrite(IgnitionPin, ignitionState); //ignition OFF
digitalWrite(RedLed, HIGH); //error indicator ON
digitalWrite(GreenLed, LOW); //run indicator OFF
digitalWrite(YellowLed, LOW); //standby indicator OFF
genIsRunning = false;
genPower = false;
}
//reset errors via button
if (isButtonTriggered()) {
digitalWrite(RedLed, LOW); //error indicator OFF
digitalWrite(YellowLed, HIGH); //standby indicator ON
totalRunTime = readULong(0);
totalRunTime = totalRunTime + (stopTime - startTime)/1000; //calculate total generator runtime
writeULong(0, totalRunTime);
genError = false; //reset errors
stopByButton = false; //reset
genStartCount = 0; //reset
runByButton = false; //prevent restart if previously started by button
runBySchedule = false; //prevent restart if previously started by schedule
}
} //end of generator error handling
//normal generator operation routine
while (genError == false) {
bool btnPressedNow = isButtonTriggered();
//check if generator is running
if (digitalRead(GenRunPin) == HIGH) {
genIsRunning = false;
} else {
genIsRunning = true;
}
//generator idle indication
if (genIsRunning == false && timerStop == false) {
digitalWrite(GreenLed, LOW); //run indicator OFF
digitalWrite(YellowLed, HIGH); //idle indicator ON
digitalWrite(RedLed, LOW); //error indicator OFF
readylcdprint(totalRunTime); //display ready message on LCD
}
//generator pause indication
if (genIsRunning == false && timerStop == true) {
if (millis() - previousMillis >= 500) { //run indicator blinking
previousMillis = millis();
ledState = (ledState == HIGH) ? LOW: HIGH;
digitalWrite(GreenLed, ledState);
}
digitalWrite(YellowLed, LOW); //idle indicator OFF
digitalWrite(RedLed, LOW); // error indicator OFF
pauselcdprint(totalRunTime, stopTime); //display pause message on LCD
}
while (genIsRunning == true) {
bool btnPressedNow = isButtonTriggered(); //сapture button press once per loop iteration to use in multiple checks
if (genError == true) { //sinhronize variables
genIsRunning = false;
}
runTime = (millis() - startTime)/1000; //calculate runtime (sec)
digitalWrite(GreenLed, HIGH); //run indicator ON
digitalWrite(YellowLed, LOW); //idle indicator OFF
//run indication
if (genIsCoolingDown == false) { //alternator is not cooling down
if (millis() - startTime < warmCoolTime*1000) { //engine is warming up
warmlcdprint(startTime, runTime);
} else {
runlcdprint(runTime); //normal run
}
} else {
coolinglcdprint(coolingStartTime, runTime); //cooling down
}
//generator stop routine
if (genIsCoolingDown == false) {
if ((digitalRead(CityVPin) == LOW && runBySchedule == false && runByButton == false) || timerStop == true || scheduleStop == true || stopByButton == true) {
digitalWrite(PowerPin, LOW); //switch generator power OFF
genPower = false;
genIsCoolingDown = true; //alternator requires cooling (run without load)
coolingStartTime = millis();
}
}
if (genIsCoolingDown == true && (millis() - coolingStartTime >= warmCoolTime*1000)) { //run without load for alternator cooling
ignitionState = LOW;
digitalWrite(IgnitionPin, ignitionState); //ignition OFF
stopTime = millis();
genStartCount = 0; //reset
genIsRunning = false;
genIsCoolingDown = false;
runBySchedule = false;
scheduleStop = false;
runByButton = false;
stopByButton = false;
totalRunTime = readULong(0);
totalRunTime = totalRunTime + (stopTime - startTime)/1000; //total run time (sec)
writeULong(0, totalRunTime);
}
//generator error handling routine
//oil level sensor error
if (digitalRead(OilPin) == HIGH) {
genError = true;
stopTime = millis();
warninglcdprint("Check oil! ");
}
//engine stall
if (ignitionState == HIGH && digitalRead(GenRunPin) == HIGH) {
digitalWrite(GreenLed, LOW); //run indicator OFF
genIsRunning = false;
if (genStartCount == 3) { //engine contantly stall
genError = true;
stopTime = millis();
warninglcdprint("Constant stall! ");
}
}
//generator power loss while running
if (genPower == true && digitalRead(GenVPin) == HIGH && digitalRead (GenRunPin) == LOW) {
digitalWrite(GreenLed, LOW); //run indicator OFF
genError = true;
stopTime = millis();
warninglcdprint("Check switch! ");
}
//forced stop via button
if (btnPressedNow == true && runByButton == false) {
genError = true;
stopTime = millis();
warninglcdprint("Stopped manualy!");
}
//end of error handling routine
//stop due to timer
if ((millis() - startTime)/1000 >= workTimePermit) {
timerStop = true;
}
if (runBySchedule == true && ((millis() - startTime)/1000 >= workTimeSchedule)) {
scheduleStop = true;
}
//stop by button when started by button
if (btnPressedNow == true && runByButton == true) {
stopByButton = true;
}
// switch power ON
if (genIsRunning == true && genIsCoolingDown == false && genPower == false && digitalRead(CityVPin) == HIGH && millis() - startTime >= warmCoolTime*1000) { //check if generator is warmed up & city power still off
digitalWrite(PowerPin, HIGH); //swich load ON
delay(200);
if (digitalRead(GenVPin) == HIGH) { //no power from generator
genError = true;
genPower = false;
stopTime = millis();
warninglcdprint("SwitchON failed!");
} else {
genPower = true;
}
}
} //end of generator running loop
//reset stop timer
if ((millis() - stopTime)/1000 >= idleTime) {
timerStop = false;
}
//override idle timer via button
if (btnPressedNow && timerStop == true) {
timerStop = false;
}
//run by external schedule relay
if (digitalRead(SchedulePin) == HIGH) {
runBySchedule = true;
}
//run by button
if (btnPressedNow) {
runByButton = true;
}
//generator start routine
if ((digitalRead(CityVPin) == HIGH || runByButton == true || runBySchedule == true) && digitalRead(GenRunPin) == HIGH && timerStop == false && genStartCount <= 3) {
while (genError == false && genIsRunning == false) { //generator is not running but is ready to start
lcd.clear();
lcd.print("Starting...");
digitalWrite(PowerPin, LOW); //make sure that generator power is OFF
genPower = false;
ignitionState = HIGH;
digitalWrite(IgnitionPin, ignitionState); //ignition ON
for (int i = 3; i > 0; i = i - 1) { //attempt to start generator 'i' times; increase starter runtime duration and pause with each attempt
if (chokeClose == false) {
digitalWrite(ChokeOnPin, HIGH); //close choke
delay(chokeActTime); //choke actuator runnig time
digitalWrite(ChokeOnPin, LOW);
chokeClose = true;
}
delay (startPause*1000+(3-i)*5000); //timeout between start attempts
digitalWrite(StarterPin, HIGH); //starter run
delay(startOnTime*1000+(3-i)*1000); //starter running time
digitalWrite(StarterPin, LOW);
delay(2000); //delay before checking start status
if (digitalRead(GenRunPin) == LOW) { //check if generator is running
digitalWrite(ChokeOffPin, HIGH); //open choke
delay(chokeActTime);
digitalWrite(ChokeOffPin, LOW);
chokeClose = false;
}
delay (3000); //delay before checking if generator is running ok with open choke
if (digitalRead(GenRunPin) == LOW) {
i = 0; //stop start attempts
startTime = millis();
genIsRunning = true;
genStartCount = genStartCount + 1;
}
if (digitalRead(GenRunPin) == HIGH && i == 1) { //error if generator has not started after all attempts
genError = true;
warninglcdprint("Start failed! ");
}
}
}
} //generator start procedure end
} //normal generator operation routine end
} //end of main loop
//DEBOUNCE FUNCTION: Returns TRUE only when button goes from HIGH to LOW and stays stable for debounceDelay
bool isButtonTriggered() {
byte reading = digitalRead(ButtonPin);
bool isPressed = false;
if (reading != lastButtonState) { //if the switch changed, due to noise or pressing
lastDebounceTime = millis(); //reset the debouncing timer
}
if (millis() - lastDebounceTime > debounceDelay) { //whatever the reading is at, it's been there for longer than the debounce delay, so take it as the actual current state
if (reading != buttonState) { //if the button state has changed
buttonState = reading;
if (buttonState == LOW) { //only return true if the new button state is LOW (Pressed)
isPressed = true;
}
}
}
lastButtonState = reading; //save the reading. Next time through the loop, it'll be the lastButtonState
return isPressed;
}
//print inital screen
void verlcdprint (byte major, byte minor) {
lcd.setCursor(0, 0);
lcd.print("<Gen Auto Start>");
char buffer[16];
sprintf(buffer, "version %02d.%02d ", major, minor); //version show
lcd.setCursor(0, 1);
lcd.print(buffer);
delay(1000); //duration to show initial screen
}
//print "run" message
void runlcdprint (unsigned int secTime) { //running time in seconds
lcd.setCursor(0, 0);
lcd.print("Running... ");
byte runTimeHH = secTime/60; //hours
byte runTimeMM = (secTime%60)/1; //minutes
char buffer[5];
sprintf(buffer, "%02d:%02d", runTimeHH, runTimeMM);
lcd.setCursor(0, 1);
lcd.print(buffer);
lcd.setCursor(5, 1);
lcd.print(" ");
}
//print "warming-up" message
void warmlcdprint (unsigned long msecStart, unsigned int secTime) { //running time (sec)
lcd.setCursor(0, 0);
lcd.print("Running... ");
byte runTimeHH = secTime/60; //hours
byte runTimeMM = (secTime%60)/1; //minutes
char buffer[5];
sprintf(buffer, "%02d:%02d", runTimeHH, runTimeMM);
lcd.setCursor(0, 1);
lcd.print(buffer);
lcd.setCursor(5, 1);
lcd.print(" warm up");
unsigned long remTime = (warmCoolTime - (millis() - startTime)/1000); //remaining warming time (sec)
byte remTimeM = remTime/60; //minutes
byte remTimeS = (remTime%60); //seconds
char bufferR[5];
sprintf(bufferR, "%02d:%02d", remTimeM, remTimeS);
lcd.setCursor(11, 0);
lcd.print(bufferR);
}
//print "cooling down" message
void coolinglcdprint (unsigned long mcoolStop, unsigned int secTime) { //cooling start time (ms), running time (sec)
lcd.setCursor(0, 0);
lcd.print("Stopping...");
byte runTimeHH = secTime/60; //hours
byte runTimeMM = (secTime%60)/1; //minutes
char buffer[5];
sprintf(buffer, "%02d:%02d", runTimeHH, runTimeMM);
lcd.setCursor(0, 1);
lcd.print(buffer);
lcd.setCursor(5, 1);
lcd.print(" cool down");
unsigned long remTime = (warmCoolTime - (millis() - mcoolStop)/1000); //remaining cooling time, seconds
byte remTimeM = remTime/60; //minutes
byte remTimeS = (remTime%60); //seconds
char bufferR[5];
sprintf(bufferR, "%02d:%02d", remTimeM, remTimeS);
lcd.setCursor(11, 0);
lcd.print(bufferR);
}
//print "pause" message
void pauselcdprint(unsigned long totalTime, unsigned long msecStop) { //total running time (sec) and stop time (ms)
lcd.setCursor(0, 0);
lcd.print(" PAUSED ");
unsigned int totalTimeH = totalTime/60; //convert seconds to hours
char buffer[5];
sprintf(buffer, "%05d", totalTimeH);
lcd.setCursor(0, 1);
lcd.print(buffer);
lcd.print(" ");
unsigned long remTime = (idleTime - (millis() - msecStop)/1000); //remaining pause, seconds
byte remTimeHH = remTime/60; //hours
byte remTimeMM = (remTime%60)/1; //minutes
char bufferR[5];
sprintf(bufferR, "%02d:%02d", remTimeHH, remTimeMM);
lcd.setCursor(11, 1);
lcd.print(bufferR);
}
//print "ready" message
void readylcdprint(unsigned long totalTime) {
lcd.setCursor(0, 0);
lcd.print(" READY ");
unsigned int totalTimeH = totalTime/60; //convert seconds to hours
char buffer[5];
sprintf(buffer, "%05d", totalTimeH);
lcd.setCursor(0, 1);
lcd.print(buffer);
lcd.setCursor(5, 1);
lcd.print(" ");
}
//print "warning" message
void warninglcdprint (String warningMessage) {
lcd.setCursor(0, 0);
lcd.print(" WARNING! ");
lcd.setCursor(0, 1);
lcd.print(warningMessage);
}
//store value in eeprom
void writeULong(int address, unsigned long number) {
EEPROM.put(address, number);
}
unsigned long readULong(int address) {
unsigned long number;
EEPROM.get(address, number);
return number;
}