// 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
#if DEBUG
#define DEBUG_PRINT(...) Serial.print(__VA_ARGS__)
#else
#define DEBUG_PRINT(...)
#endif
constexpr uint8_t MAINSsupply {7}; // AC power detection using a 5v charger
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
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;
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 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
switch (state)
{ case State::INITIALIZE :
DEBUG_PRINT('.');
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 :
DEBUG_PRINT('I');
if (inputState == LOW && previousInputState == HIGH) // check for AC fail
{
Serial.println(F("\nAC off - Enter WARNING"));
previousMillis = millis();
state = State::WARNING;
}
break;
case State::WARNING :
DEBUG_PRINT('W');
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 :
DEBUG_PRINT('m'); // lower m for 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 :
DEBUG_PRINT('U'); // upper U for ON
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"));
upsSwitch.pulse(intervalOn); // switching UPS ON
state = State::FAIL;
}
break;
case State::FAIL :
DEBUG_PRINT('X');
if (inputState == HIGH && previousInputState == LOW)
{
Serial.println(F("\nAC on --> Checking If Mains is Stable"));
previousMillis = millis();
state = State::RECOVER;
}
break;
case State::RECOVER :
DEBUG_PRINT('R');
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 :
DEBUG_PRINT('u'); // lower u for 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 :
DEBUG_PRINT('B');
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();
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() {
Serial.begin(115200);
Serial.println(F("Initializing Please Wait..."));
upsSwitch.begin();
pinMode(UPSrelay, OUTPUT);
pinMode(MAINSrelay, OUTPUT);
delay(50);
}
void loop() {
checkAC();
upsSwitch.update(); // must be called in loop - no delays allowed ;-)
#if DEBUG
delay(500); // this is only to see which state is active, remove that in production
#endif
}