// Uninterrupted Power supply.
// using a finite state machine
// By Dinesh Bhardwaj ([email protected])
#define DEBUG 1 // 1 debug messages, 0 off
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#if defined(ARDUINO) && ARDUINO >= 100
#define printByte(args) write(args);
#else
#define printByte(args) print(args,BYTE);
#endif
LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display
constexpr uint8_t MAINSsupply {7}; // AC power detection using a 5v charger
constexpr uint8_t button {8}; // Push button detection
constexpr uint8_t UPSrelay {9}; // UPS relay
//constexpr uint8_t upsSwitch {11}; // connected to UPS on/off switch
constexpr uint8_t MAINSrelay {12}; // MAINS relay
constexpr uint16_t intervalOn {1000}; // pulse time for UPS ON signal
constexpr uint16_t intervalOff {3000}; // pulse time for UPS OFF signal
constexpr uint16_t intervalWarning {3000}; // time before UPS will get switched on
constexpr uint16_t intervalRecover {5000}; // time before UPS will be switched off
constexpr uint16_t intervalCheckMAINS {10000}; // MAINS checking Time
constexpr uint16_t intervalLCD {30000}; // LCD light TURN ON Time
enum class State {INITIALIZE, // Check if Mains is available
IDLE, // AC is on, waiting fo AC black out
WARNING, // AC is off, waiting for UPS switch on
MAINSRELAY_OFF,
UPSRELAY_ON,
FAIL, // AC is off, waiting for AC coming back
RECOVER, // AC is on, recovering period, AC is back but we keep UPS running
UPSRELAY_OFF, // UPS RELAY Off
BACK // preparing things back for idle
};
// a class to pulse a pin - nonblocking
class PulsePin
{
const uint8_t pin;
uint32_t previousMillis = 0;
uint32_t lcdPreviousMillis = 0;
bool state = LOW; // state of the pin
uint16_t interval = intervalOn;
public:
PulsePin (const uint8_t pin ) : pin (pin) {}
void begin()
{
pinMode(pin, OUTPUT);
}
void pulse(uint16_t interval)
{
this->interval = interval;
digitalWrite(pin, HIGH);
previousMillis = millis();
state = HIGH;
}
void update()
{
if (state && millis() - previousMillis > interval )
{
digitalWrite(pin, LOW);
state = LOW;
}
}
};
PulsePin upsSwitch(11);
void checkAC()
{
static uint32_t previousMillis = 0; // timestamp for state machine
static uint32_t lcdPreviousMillis = 0;
static State state {State::INITIALIZE}; // current state
static uint8_t previousInputState = HIGH; // previous state of input circuit / AC
uint8_t inputState = digitalRead(MAINSsupply); // current state of input circuit / AC
static uint8_t previousinputbutton = HIGH; // previous state of push button
uint8_t inputbutton = digitalRead(button); // current state of push button
switch (state)
{ case State::INITIALIZE :
lcd.setCursor(0, 0 );
lcd.print("INITIALIZING ");
lcd.setCursor(0, 1 );
lcd.print("PLEASE WAIT... ");
if (inputState == HIGH ) // check for AC presence
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC ON - Enter IDLE"));
digitalWrite(UPSrelay, LOW); // UPS relay turned OFF
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF(MAINS ON)
previousMillis = millis();
state = State::IDLE;
}
if (inputState == LOW ) // check for AC fail
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC off - Enter WARNING"));
previousMillis = millis();
state = State::WARNING;
}
break;
case State::IDLE :
lcd.setCursor(0, 0);
lcd.print(" IDLE... ");
lcd.setCursor(0, 1);
lcd.print(" ");
if (millis() - lcdPreviousMillis>=intervalLCD)
{
lcd.noBacklight();
}
if (digitalRead(button) == HIGH)
{
Serial.println(F("\Button Pressed"));
previousMillis = millis();
lcdPreviousMillis = millis();
lcd.backlight();
}
if (millis() - lcdPreviousMillis>=intervalLCD)
{
lcd.noBacklight();
}
if (inputState == LOW && previousInputState == HIGH) // check for AC fail
{
Serial.println(F("\nAC off - Enter WARNING"));
previousMillis = millis();
lcdPreviousMillis = millis();
lcd.backlight();
state = State::WARNING;
}
break;
case State::WARNING :
lcd.setCursor(0, 0 );
lcd.print("WARNING ");
lcd.setCursor(0, 1 );
lcd.print("POWER FAIL ");
if (inputState == HIGH && previousInputState == LOW) // check for AC back
{
Serial.println(F("\nAC on again - go back to IDLE"));
state = State::IDLE;
}
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC off --> MAINSRELAY_OFF"));
digitalWrite(MAINSrelay, HIGH); // MAINS relay turned ON (MAINS OFF)
previousMillis = millis();
state = State::MAINSRELAY_OFF;
}
break;
case State::MAINSRELAY_OFF :
lcd.setCursor(0, 0 );
lcd.print("TURNING OFF ");
lcd.setCursor(0, 1 );
lcd.print("MAINS RELAY ");
if (inputState == HIGH && previousInputState == LOW) // check for AC back again
{
Serial.println(F("\nAC on again Turning MAINSrealy ON and go back to IDLE"));
previousMillis = millis();
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF again (MAINS ON)
state = State::IDLE;
}
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC off --> UPSRELAY_ON"));
digitalWrite(UPSrelay, HIGH); // UPS relay turned ON
previousMillis = millis();
state = State::UPSRELAY_ON;
}
break;
case State::UPSRELAY_ON :
lcd.setCursor(0, 0 );
lcd.print("TURNING ON ");
lcd.setCursor(0, 1 );
lcd.print(" UPS ");
if (inputState == HIGH && previousInputState == LOW) // check for AC back again
{
Serial.println(F("\nAC on again Turning MAINSrealy ON and go back to IDLE"));
previousMillis = millis();
digitalWrite(UPSrelay, LOW); // UPS relay turned OFF again
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF again (MAINS ON)
state = State::IDLE;
}
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC off --> FAIL"));
lcdPreviousMillis = millis();
upsSwitch.pulse(intervalOn); // switching UPS ON
state = State::FAIL;
}
break;
case State::FAIL :
lcd.setCursor(0, 0 );
lcd.print("POWER FAIL ");
lcd.setCursor(0, 1 );
lcd.print(" LOAD ON UPS ");
if (millis() - lcdPreviousMillis>=intervalLCD)
{
lcd.noBacklight();
}
if (inputState == HIGH && previousInputState == LOW)
{
Serial.println(F("\nAC on --> Checking If Mains is Stable"));
previousMillis = millis();
lcd.backlight();
state = State::RECOVER;
}
break;
case State::RECOVER :
lcd.setCursor(0, 0 );
lcd.print("MAINS RESTORED");
lcd.setCursor(0, 1 );
lcd.print("SWITCHINGTOMAINS");
if (inputState == LOW && previousInputState == HIGH)
{
Serial.println(F("\nAC off - Not Stable --> go back to FAIL"));
state = State::FAIL;
}
if (millis() - previousMillis > intervalCheckMAINS)
{
Serial.println(F("\nAC on - MAINS Stable --> UPSRELAY_OFF"));
digitalWrite(UPSrelay, LOW); // UPS relay turned OFF
previousMillis = millis();
state = State::UPSRELAY_OFF;
}
break;
case State::UPSRELAY_OFF :
lcd.setCursor(0, 0 );
lcd.print("TURNING OFF ");
lcd.setCursor(0, 1 );
lcd.print("UPS RELAY ");
if (inputState == LOW && previousInputState == HIGH) // check for AC fail again in between
{
Serial.println(F("\nAC OFF in Between, Turning UPSRELAY ON again"));
digitalWrite(UPSrelay, HIGH); // UPS relay turned ON again
previousMillis = millis();
state = State::FAIL;
}
if (millis() - previousMillis > intervalRecover)
{
Serial.println(F("\nAC on --> Turn OFF UPS"));
upsSwitch.pulse(intervalOff); // switching UPS OFF
previousMillis = millis();
state = State::BACK;
}
break;
case State::BACK :
lcd.setCursor(0, 0 );
lcd.print("GOING BACK TO ");
lcd.setCursor(0, 1 );
lcd.print("IDLE MODE ");
if (inputState == LOW && previousInputState == HIGH) // check for AC fail again in between
{
Serial.println(F("\nAC OFF in Between, Turning UPSRELAY ON again"));
previousMillis = millis();
state = State::INITIALIZE;
}
if (millis() - previousMillis > intervalRecover)
{
Serial.println(F("\nAC on - longer than timelimit --> IDLE again"));
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF (MAINS ON)
previousMillis = millis();
lcdPreviousMillis = millis();
state = State::IDLE;
}
break;
}
previousInputState = inputState;
delay(20); // this dirty delay is just to debounce switches
}
// this function gets called during delays.
// so we use it also to check if we have to do something with the pulsePin
void yield()
{
upsSwitch.update();
}
void setup() {
lcd.init(); // initialize the lcd
lcd.backlight();
Serial.begin(115200);
Serial.println(F("Initializing Please Wait..."));
upsSwitch.begin();
pinMode(UPSrelay, OUTPUT);
pinMode(MAINSrelay, OUTPUT);
pinMode(button, INPUT);
delay(50);
}
void loop() {
checkAC();
upsSwitch.update(); // must be called in loop - no delays allowed ;-)
}