// pulse an output if switch changes to low
// using a finite state machine
// https://forum.arduino.cc/t/switching-circuit-using-relays-and-ups-with-arduino/1019626/
#define DEBUG 1 // 1 debug messages, 0 off
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
int period = 5000; // Voltage update delay time (ms)
unsigned long time_now = 0;
#define SIGNAL_PIN A0 // Define analog input
float adc_voltage = 0.0; // Floats for ADC voltage
float in_voltage = 0.0; // Floats for Input voltage
float R1 = 100000.0; // Resistor values in divider (in ohms)
float R2 = 20000.0; // Resistor values in divider (in ohms)
float ref_voltage = 5.0; // Float for Reference Voltage
int adc_value = 0; // Integer for ADC value
#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 FANrelay {6}; // Cooling FAN relay
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 CHARGErelay {10}; // Battery Charger 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... ");
lcd.setCursor(0, 0 );
lcd.print("MAINS Relay ON ");
digitalWrite(MAINSrelay, HIGH);
delay(intervalCheckMAINS);
lcd.setCursor(0, 0 );
lcd.print("MAINS Relay OFF ");
digitalWrite(MAINSrelay, LOW);
delay(intervaloff);
lcd.setCursor(0, 0 );
lcd.print("UPS Relay ON ");
digitalWrite(UPSrelay, HIGH);
delay(intervalCheckMAINS);
lcd.setCursor(0, 0 );
lcd.print("UPS Relay OFF ");
digitalWrite(UPSrelay, LOW);
delay(intervaloff);
lcd.setCursor(0, 0 );
lcd.print("CHARGER Relay ON ");
digitalWrite(CHARGErelay, HIGH);
delay(intervalCheckMAINS);
lcd.setCursor(0, 0 );
lcd.print("CHARGER Relay OFF ");
digitalWrite(CHARGErelay, LOW);
delay(intervaloff);
lcd.setCursor(0, 0 );
lcd.print("FAN Relay ON ");
digitalWrite(FANrelay, HIGH);
delay(intervalCheckMAINS);
lcd.setCursor(0, 0 );
lcd.print("FAN Relay OFF ");
digitalWrite(FANrelay, LOW);
delay(intervaloff);
lcd.setCursor(0, 0 );
lcd.print("UPS Switch ON ");
upsSwitch.pulse(intervalCheckMAINS); // switching UPS ON
lcd.setCursor(0, 0 );
lcd.print("UPS Switch OFF ");
lcd.setCursor(0, 0 );
lcd.print(" THE END ");
delay(intervalCheckMAINS);
{
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();
lcd.clear();
state = State::IDLE;
}
if (inputState == LOW ) // check for AC fail
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC off - Enter WARNING"));
previousMillis = millis();
lcd.clear();
state = State::WARNING;
}
break;
case State::IDLE :
lcd.setCursor(0, 0);
lcd.print("IDLE... ");
lcd.setCursor(0, 1);
lcd.print(" ");
adc_value = analogRead(SIGNAL_PIN);
in_voltage = adc_value * (5.0/1024)*((R1 + R2)/R2);
in_voltage = 0.97222 * in_voltage;
if(millis() >= time_now + period){
time_now += period;
Serial.print("Input Voltage = ");
Serial.println(in_voltage, 2);
lcd. setCursor (10, 0);
lcd.print (in_voltage, 2);
lcd.print ("V");
Serial.println(" ");
}
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 ");
lcd.setCursor(0, 1 );
lcd.print("MAINS RELAY OFF ");
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 ");
lcd.setCursor(0, 1 );
lcd.print(" ON 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();
previousMillis = millis();
upsSwitch.pulse(intervalOn); // switching UPS ON
state = State::FAIL;
}
break;
case State::FAIL :
lcd.setCursor(0, 0 );
lcd.print("MAINS FAIL");
lcd.setCursor(0, 1 );
lcd.print(" LOAD ON UPS ");
adc_value = analogRead(SIGNAL_PIN);
in_voltage = adc_value * (5.0/1024)*((R1 + R2)/R2);
in_voltage = 0.97222 * in_voltage;
if(millis() >= time_now + period){
time_now += period;
Serial.print("Input Voltage = ");
Serial.println(in_voltage, 2);
lcd. setCursor (10, 0);
lcd.print (in_voltage, 2);
lcd.print ("V");
Serial.println(" ");
}
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 == 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 OK ");
lcd.setCursor(0, 1 );
lcd.print("SWITCHINGTOMAINS");
if (inputState == LOW && previousInputState == HIGH)
{
Serial.println(F("\nAC off - Not Stable --> go back to FAIL"));
lcd.backlight();
lcdPreviousMillis = millis();
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 ");
lcd.setCursor(0, 1 );
lcd.print("UPS RELAY OFF ");
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");
lcd.setCursor(0, 1 );
lcd.print(" TO 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(CHARGErelay, OUTPUT);
pinMode(FANrelay, OUTPUT);
pinMode(button, INPUT);
delay(50);
}
void loop() {
checkAC();
upsSwitch.update(); // must be called in loop - no delays allowed ;-)
}