// Forum: https://forum.arduino.cc/t/arduino-millis-steps-for-relay-control-randomly-crashes/1232885
// This Wokwi project: https://wokwi.com/projects/391753526969188353

#define RELAYONE 11
#define RELAYTWO 10
#define SW1_PIN 9
#define LEDSTAT 8

const unsigned long RELAY_ON_INTERVAL = 9000;
const unsigned long PAUSE_INTERVAL = 500;
const unsigned long MOTOR_OFF_INTERVAL = 500; /* not using this */
const unsigned long SPARE_INTERVAL = 750; /* not using this */
unsigned long startMillis;

// true: RELAY signal is ON
// false: RELAY signal is OFF
bool isRELAY = false;

// Tracks the current steps
uint8_t steps = 0;

// Holds the current state
bool relay_one_state = false;
bool relay_two_state = false;
bool led_state = false;

// This is for button debouncing
bool currState = HIGH;
bool prevState = HIGH;
bool buttonState = HIGH;
unsigned long debounceStart = 0;
unsigned long debounceDuration = 50;


void setup() {
  pinMode(RELAYONE, OUTPUT);
  pinMode(RELAYTWO, OUTPUT);
  pinMode(SW1_PIN, INPUT_PULLUP);
  pinMode(LEDSTAT, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  /*
      Here is the breakdown of the steps:
      0:  RELAYONE is on (high)
      1:  off
      2:  RELAYTWO is on (high)
      3:  off
  */

  // Logic for the RELAY
  //  Pseudo code:
  //    if RELAY is ON, check the current steps
  //      if steps is 1 and 3
  //        send PAUSE_INTERVAL
  //      if steps is 0
  //        send RELAY_ON_INTERVAL
  //      if steps is 2
  //        send RELAY_ON_INTERVAL
  //  The duration of each signal is achieve using
  //    the millis() function
  //    For example:
  //      if ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) {
  //        steps++;
  //        startMillis = millis(); // save the start time
  //      }
  //    The ( millis() - startMillis ) is basically the elapse time
  //      since the startMillis is recorded
  //      so that ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) is
  //      basically for checking if the required interval is achieve.
  //      If it becomes true, increment the counter and record the time
  //      and so on
  if (isRELAY) {

    if        ( ( steps == 1  ) ||
                ( steps == 3  ) ) {
      Serial.println("pause");
      relay_one_state = false;
      relay_two_state = false;
      if ( ( millis() - startMillis ) >= PAUSE_INTERVAL ) {
        steps++;
        startMillis = millis(); // save the start time
      }
    } else if ( ( steps == 0 )) {
      Serial.println("step 0");
      relay_one_state = true;
      relay_two_state = false;
      led_state = true;
      if ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) {
        // signal interval is complete
        // move to the next step
        steps++;
        startMillis = millis(); // save the start time
      }
    } else if ( ( steps == 2  ) ) {
      Serial.println("step 2");
      relay_one_state = false;
      relay_two_state = true;
      if ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) {
        steps++;
        startMillis = millis(); // save the start time
      }
    } else {
      relay_one_state = false;
      relay_two_state = false;

      if ( ( millis() - startMillis ) >= MOTOR_OFF_INTERVAL ) {
        steps++;
        startMillis = millis(); // save the start time
      }
    }
    // if the steps reach steps 5, go back to steps 0
    if (steps > 4) steps = 0;
  }

  // Update the output signal
  digitalWrite(RELAYONE, relay_one_state);
  digitalWrite(RELAYTWO, relay_two_state);
  digitalWrite (LEDSTAT, led_state);

  /*
      This is for debouncing the tactile switch
  */
  currState = digitalRead(SW1_PIN);
  if (currState != prevState) {
    debounceStart = millis();
  }

  if ((millis() - debounceStart) > debounceDuration) {
    if (currState != buttonState) {
      buttonState = currState;
      if (currState == LOW) {
        if ( isRELAY ) {
          // currently sending motor sequence, now turn it off
          isRELAY = false;
          Serial.println("off");
          relay_one_state = false;
          relay_two_state = false;
          led_state = false;
        } else {
          // currently motor sequence is off, now turn it on
          isRELAY = true;
          startMillis = millis();
          Serial.println("on");
          steps = 0;
        }
      }
    }
  }
  prevState = currState;

  // Do other stuffs here, without blocking

}
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
relays are active high
Arduino + millis + steps for relay control randomly crashes