// https://wokwi.com/projects/406405729223101441
// https://forum.arduino.cc/t/advice-on-finite-state-machine-code-to-move-two-motors-with-two-buttons-and-3-endstops/1284857

# include <AccelStepper.h>
# include <ezButton.h>
# include <LiquidCrystal_I2C.h>

unsigned long now;    //... current value of millis() for the loop pass

// Pin Definitions...
# define BTN_R 2 // Right button
# define BTN_L 3 // Left button

# define END_R 4 // Right endstop
# define END_M 5 // Middle endstop
# define END_L 6 // Left endstop

# define SSR A3 // SSR to power powersupply for drivers

// Stepper motor pins for the left stepper
# define STEP_L 7 // Step pin for the left stepper motor
# define DIR_L 8 // Direction pin for the left stepper motor
# define ENA_L 9 // Enable pin for the left stepper motor

// Stepper motor pins for the right stepper
# define STEP_R 10 // Step pin for the right stepper motor
# define DIR_R 11 // Direction pin for the right stepper motor
# define ENA_R 12 // Enable pin for the right stepper motor

// Alarm pin on internal interrupt to stop movement if something happens.
# define ALM 2 // Alarm from both stepper drivers into one internal interrupt

// Stepper Initialization
AccelStepper stepperL(AccelStepper::DRIVER, STEP_L, DIR_L);
AccelStepper stepperR(AccelStepper::DRIVER, STEP_R, DIR_R);

// LCD panel
# define I2C_ADDR    0x27
# define LCD_COLUMNS 16
# define LCD_LINES   2

LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);

// yeah, just two ezButtons
ezButton button[2] = {BTN_R, BTN_L}; // 5, END_R, END_M, END_L};
const byte NEZBUTTONS = sizeof button / sizeof *button;

# define PRESST LOW   // ezButton state for a pressed button

// button "nicknames" for better readability
enum buttonNames {RIGHT = 0, LEFT};
char *buttonTags[] = {"Right", "Left",};

bool fork = false; // To indicate logic errors

// FSM states
enum State {
  IDLE,
  RIGHT_RIGHTWARDS,
  LEFT_LEFTWARDS,
  LEFT_RIGHTWARDS,
  RIGHT_LEFTWARDS,
  NONE,
};

char *stateTags[] = {"Idle", "R2R", "L2L", "L2R", "R2L", "NONE",};
State state = IDLE;

// Timers and Debounce
const unsigned long SSR_DEACTIVATION_TIME = 5 * 60 * 1000; // 5 minutes - sleep mode for SSR to save SSR itself and PS
unsigned long lastButtonPressTime = 0;

void setup() {
  Serial.begin(115200);

  setupLCD();

  pinMode(25, OUTPUT);
  pinMode(27, OUTPUT);

  // Initialize the buttons, set the debounce
  for (int ii = 0; ii < NEZBUTTONS; ii++)
    button[ii].setDebounceTime(20);

  // Initialize the endstops
  pinMode(END_R, INPUT_PULLUP);
  pinMode(END_M, INPUT_PULLUP);
  pinMode(END_L, INPUT_PULLUP);

  // Initialize the left stepper motor
  stepperL.setMaxSpeed(200);
  stepperL.setSpeed(200);
  stepperL.setAcceleration(100);
  stepperL.setEnablePin(ENA_L);
  stepperL.setPinsInverted(false, false, true);
  stepperL.disableOutputs();

  // Initialize the right stepper motor
  stepperR.setMaxSpeed(200);
  stepperR.setSpeed(200);
  stepperR.setAcceleration(100);
  stepperR.setEnablePin(ENA_R);
  stepperR.setPinsInverted(false, false, true);
  stepperR.disableOutputs();

  // SSR
  pinMode(SSR, OUTPUT);
  digitalWrite(SSR, HIGH); // Activate SSR at startup
}

void setupLCD()
{
  lcd.init();
  lcd.backlight();

  // Print something
  lcd.setCursor(0, 0);
  lcd.print("Hello");
  lcd.setCursor(0, 1);
  lcd.print("World!");

  delay(333);
}

void reportLCD()
{
  static unsigned long lastTime;

  if (now - lastTime < 100) return;

  lastTime = now;
  
  lcd.setCursor(0, 0);
  lcd.print("          ");
  lcd.setCursor(0, 0); 
  lcd.print(stepperL.currentPosition());

  lcd.setCursor(0, 1);
  lcd.print("          ");
  lcd.setCursor(0, 1);
  lcd.print(stepperR.currentPosition());
}

# define ASSERTED LOW

void loop() {
  now = millis();

  stepperL.run();
  stepperR.run();
  reportLCD();

  // INPUT the user pushbuttons and the limit switches
  for (int ii = 0; ii < NEZBUTTONS; ii++) {
    button[ii].loop();
  }

  bool limitRight = digitalRead(END_R) == ASSERTED;
  bool limitMiddle = digitalRead(END_M) == ASSERTED;
  bool limitLeft = digitalRead(END_L) == ASSERTED;


//... not yet. lastButtonPressTime is the last _loop_ time. SSR will never be deactivated
  lastButtonPressTime = now;
  digitalWrite(SSR, HIGH); // Activate SSR


  reportState();

  static bool once = false;  // hack to provide a one-shot operation in a repeated case


//...
static bool needJamLeft;
static long jamThisLeft;


  // FSM State Machine
  switch (state) {
    case IDLE :
    if (!stepperL.isRunning() && needJamLeft) {
      stepperL.setCurrentPosition(jamThisLeft);
      needJamLeft = false;
    }
  
    if (button[RIGHT].isPressed()) {
        if (!limitRight) {
            state = RIGHT_RIGHTWARDS; once = true;
        } else { // Right button pressed AND right limit reached
            state = LEFT_RIGHTWARDS; once = true;
        }
    } else if (button[LEFT].isPressed()) {
        if (!limitLeft) {
            state = LEFT_LEFTWARDS; once = true;
        } else { // Left button pressed AND left limit reached
            state = RIGHT_LEFTWARDS; once = true;
        }
    }

    break;
    
    case RIGHT_RIGHTWARDS :
      if (button[RIGHT].isPressed()) {
        stepperR.stop();
        state = IDLE;
      }
      else if (limitRight) {
        stepperR.stop();
        stepperR.disableOutputs();  // stop. stepping. now.
        state = IDLE;
        if (!limitMiddle && button[RIGHT].getState() == PRESST) {
          state = LEFT_RIGHTWARDS; once = true;
        }
      }
      else if (once) {
        stepperR.enableOutputs();
        stepperR.moveTo(20000);
        once = false;
      }

      break;
      
// stop the left car if it is moving and the button gets pressed
// stop the left car at the left limit; if the button is down, poke the right car      
// otherwise start up the left car moving to the left
      
    case LEFT_LEFTWARDS :
      if (button[LEFT].isPressed()) {
        stepperL.stop();
        state = IDLE;
      }
      else if (limitLeft) {
        stepperL.stop();
        Serial.print(" left limit HALT ");
        Serial.println(stepperL.currentPosition());
        jamThisLeft = stepperL.currentPosition();
        needJamLeft = true;
        stepperL.disableOutputs();  // stop. stepping. now.
        state = IDLE;
        if (!limitMiddle && button[LEFT].getState() == PRESST) {
          state = RIGHT_LEFTWARDS; once = true;
        }
      }
      else if (once) {
        stepperL.enableOutputs();
        stepperL.moveTo(-20000);
        once = false;
      }

      break;

    case LEFT_RIGHTWARDS :
      if (button[RIGHT].isPressed() || limitMiddle) {
        stepperL.stop();
        state = IDLE;
      }
      else if (once) { stepperL.enableOutputs(); stepperL.moveTo(20000); once = false;}


      break;

    case RIGHT_LEFTWARDS :
      if (button[LEFT].isPressed() || limitMiddle) {
        stepperR.stop();
        state = IDLE;
      }
      else if (once) { stepperR.enableOutputs(); stepperR.moveTo(-20000); once = false;}

      break;
  }

  // Disable SSR after timeout
  if (now - lastButtonPressTime >= SSR_DEACTIVATION_TIME) {
    digitalWrite(SSR, LOW); // Deactivate SSR
  }

// Disable stepper outputs when idle
  if (state == IDLE) {
    stepperL.disableOutputs();
    stepperR.disableOutputs();
  }
}









// various status reportings

void reportState()
{
  static State lastState = NONE;
  if (state != lastState) {
    Serial.print("-> ");
    Serial.println(stateTags[state]);
    lastState = state;

    if (state == IDLE) {
      Serial.print(stepperL.currentPosition());
      Serial.print(" <<left: car position :right>> ");
      Serial.println(stepperR.currentPosition());
    }
  }

  motorLEDs();
}

// just some LEDs showing if the steppers are moving left or right
void motorLEDs()
{
  static int lastLeft = 32767;
  static int lastRight = 32767;

  static unsigned long lastLEDLeft;
  static unsigned long lastLEDRight;

  int leftNow = stepperL.currentPosition();

  if (lastLeft < leftNow) {
    digitalWrite(27, HIGH);
    digitalWrite(25, LOW);
    lastLEDLeft = now;
  }
  if (lastLeft > leftNow) {
    digitalWrite(25, HIGH);
    digitalWrite(27, LOW); 
    lastLEDLeft = now;    
  }
  if (lastLeft == leftNow)
    if (now - lastLEDLeft > 222) {
      digitalWrite(25, LOW); digitalWrite(27, LOW);
    }

  lastLeft = leftNow;

// second verse, much like the first...

  int rightNow = stepperR.currentPosition();
//  Serial.print(lastRight); Serial.print(" now "); Serial.println(rightNow);

  if (lastRight < rightNow) {
//Serial.println("                          lastRight < rightNow");
    digitalWrite(31, HIGH);
    digitalWrite(29, LOW);
    lastLEDRight = now;
  }
  if (lastRight > rightNow) {
//Serial.println("                                          lastRight > rightNow");
    digitalWrite(29, HIGH);
    digitalWrite(31, LOW); 
    lastLEDRight = now;    
  }
  if (lastRight == rightNow)
    if (now - lastLEDRight > 222) {
      digitalWrite(29, LOW); digitalWrite(31, LOW);
    }

  lastRight = rightNow;
}
<<LEFT........RIGHT>>
L_STOP.......M_STOP.......R_STOP
<<<<.....s p a r e . m e.....>>>>
<-R...R->
<-L...L->
A4988
A4988