// https://wokwi.com/projects/405980379522078721
// from
// https://wokwi.com/projects/405620646744181761

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

// user input  car to the Direction right car Right
# define rightRightPin 2
# define leftRightPin 7

# define rightLeftPin 3
# define leftLeftPin 8

# define fullStopPin  39
# define statsPin     37

// limit switches
# define leftEndstopPin 6
# define middleEndstopPin 5
# define rightEndstopPin 4

// mech simulation
# define leftCar  0
# define rightCar 1
# define endStop    0
# define middleStop 1
# define left (-1)
# define right 1

bool fork;  // if the mechanical simulation is asked to do the impossible this goes true.

//// now the higher level code

# include <ezButton.h>
//                           2            3             7             8
ezButton button[4] = {rightRightPin, leftRightPin, rightLeftPin, leftLeftPin};

enum buttonNames {bR2R, bL2R, bR2L, bL2L};

enum oldButtonNames {RIGHT = 0, LEFT};

char * buttonTags[] = {"R>>", "L>>", "<<R", "<<L"};

bool limitLeft;
bool limitMiddle;
bool limitRight;

void setup()
{
  // Serial print for debugging
  Serial.begin(115200);

  setupSteppers();

  pinMode(leftEndstopPin, INPUT_PULLUP);
  pinMode(middleEndstopPin, INPUT_PULLUP);
  pinMode(rightEndstopPin, INPUT_PULLUP); 

  // four ezButtons - time to think again later
  for (int ii = 0; ii < 4; ii++)
    button[ii].setDebounceTime(20);

  pinMode(fullStopPin, INPUT_PULLUP);   // true sense for fullStop
  pinMode(statsPin, INPUT_PULLUP);   // print some data...

  setupMech();
}

// limit switches normal/wiring
# define ASSERTED HIGH

// simple loop steps a cabinet if it can
void loop()
{
  if (digitalRead(statsPin) == LOW) report();
/* just the steppers ma'am
  if (0) {
    testSteppersLoop();
    return;
  }
*/
  // if the high level logic doesn't break things, this should never happen
  if (fork) {
    Serial.println("logic error");
    for (; ;);
  }

  // INPUT the user pushbuttons and the limit switches
  button[bR2R].loop();
  button[bL2R].loop();
  button[bR2L].loop();
  button[bL2L].loop();

  limitLeft = digitalRead(leftEndstopPin) == ASSERTED;
  limitMiddle = digitalRead(middleEndstopPin) == ASSERTED;
  limitRight = digitalRead(rightEndstopPin) == ASSERTED;

  process();

  stepperLoop();    // run both steppers

  updateMech();
  renderMech();

  return;
}

//void process4() { process(); }; // so sue me

void process4()
{
  static unsigned int count;
  for (unsigned char ii = 0; ii < 4; ii++)
    if (button[ii].isPressed()) {
      Serial.print(count);
      Serial.print(" ");
      Serial.println(buttonTags[ii]);
      count++;
    }

  if (button[bL2L].isPressed()) run(leftCar, endStop);
  if (button[bL2R].isPressed()) run(leftCar, middleStop);
  
}

//           Idle  LEFT_LEFTWARDS   RIGHT_LEFTWARDS   RIGHT_RIGHTWARDS   LEFT_RIGHTWARDS
enum STATES {IDLE, L2L,             R2L,              R2R,               L2R,              NONE};

char *stateTags[] = {
  "Idle.", 
  "L2L LEFT_LEFTWARDS",
  "R2L RIGHT_LEFTWARDS",
  "RLR RIGHT_RIGHTWARDS",
  "L2R LEFT_RIGHTWARDS",
};

// if true stop between cars, now on slide switch at fullStopPin
bool fullStop = true;

// first process to call on steppers
void process(void)
{
  static STATES state = IDLE;

{
  static STATES lastPrinted = NONE;
  if (lastPrinted != state) {
    Serial.print("x ");
    Serial.println(stateTags[state]);
    lastPrinted = state;
  }
}

  bool gR2R = button[bR2R].isPressed();
  bool gL2R = button[bL2R].isPressed();
  bool gR2L = button[bR2L].isPressed();
  bool gL2L = button[bL2L].isPressed();

  fullStop = digitalRead(fullStopPin) == HIGH;

// this line makes constant button pressing necessary
// choose fullStop thoughfully also:
// not yet implemented

// this stops motion in progress and eats the buttons
  if (state != IDLE)
    if (gR2R || gL2R || gR2L || gL2L) {
      state = IDLE;

      gR2R = false;
      gL2R = false;
      gR2L = false;
      gL2L = false;

      stopCars();
    }

  switch (state) {
  case IDLE :
    if (gR2R) state = R2R;
    if (gR2L) state = R2L;
    if (gL2R) state = L2R;
    if (gL2L) state = L2L;

    break;

  case R2R :
    if (limitRight) state = fullStop ? IDLE : L2R;
    else run(rightCar, endStop);
    break;

  case L2R : 
    if (limitMiddle) state = IDLE;
    else
     run(leftCar, middleStop);
    break;

  case L2L :
    if (limitLeft) state = fullStop ? IDLE : R2L;
    else run(leftCar, endStop);
    break;

  case R2L : 
    if (limitMiddle) state = IDLE;
    else 
     run(rightCar, middleStop);
    break;
  }
//  if (state == IDLE) stopCars();
}

// mostly the simulation of the mechanism
// draw the "cars", set the limit signals

// mechanical simulation now runs steppers

# include <AccelStepper.h>

# define stepPinLeft  25
# define directionPinLeft 27

# define stepPinRight  29
# define directionPinRight 31

AccelStepper leftStepper(AccelStepper::DRIVER, stepPinLeft, directionPinLeft);
AccelStepper rightStepper(AccelStepper::DRIVER, stepPinRight, directionPinRight);

const byte PositionPot = A0;
const byte AccelerationPot = A1;

//// mostly two steppers

void setupSteppers()
{
  leftStepper.setPinsInverted(false, false, true);
  leftStepper.enableOutputs();
  leftStepper.setMaxSpeed(777);
  leftStepper.setSpeed(200);

  leftStepper.setAcceleration(250);  // whatever. from the original non-motor-controller version

  rightStepper.setPinsInverted(true, false, true);
  rightStepper.enableOutputs();
  rightStepper.setMaxSpeed(777);
  rightStepper.setSpeed(200);

  rightStepper.setAcceleration(250);
}

bool isRunning;   // has been moveTo, may not be moving. NOT USED yet

void stepperLoop()
{
  rightStepper.run();
  leftStepper.run();
}

// 0 left car / 1 right car; 0 to the end stop / 1 to the middle stop
void run(int theCar, int theStop)
{

if (0) {
  static long printed = -1;
  long leftP = leftStepper.currentPosition();
  if (printed != leftP) {
    Serial.println(leftP);
    printed = leftP;
  }
}

// if (isRunning) return;

  if (!theCar) { // the left car will move
    if (!theStop) leftStepper.moveTo(0);
//    else leftStepper.moveTo(1300);
// the left car must move to meet the right car
    else {
      int posR = (3300 - rightStepper.currentPosition());
      int meeting = posR - 2000;
      leftStepper.moveTo(meeting);

//S//erial.print(" meeting "); Serial.println(meeting);

//while (leftStepper.currentPosition() != meeting) leftStepper.run();
//delay(100);


    }
  }
  else {  // the right car will move
    if (!theStop) rightStepper.moveTo(0);
//    else rightStepper.moveTo(1300);
// the right car must move to meet the left car
    else {
      int posL = leftStepper.currentPosition();
      int meeting = 3300 - (posL + 2000);
      rightStepper.moveTo(meeting);
    }
  }

  isRunning = true;
}

// did this break all hell loose? yes.
void stopCars()
{
//  if (!isRunning) return;
  isRunning = false;

// ??
//  leftStepper.moveTo(leftStepper.currentPosition());
//  rightStepper.moveTo(rightStepper.currentPosition());
// or ??
  leftStepper.stop();
  rightStepper.stop();
}

// neopixel linear graph

# define WIDTH  10    // Sry^3 might break easily
# define N_REAL (33)
# define PIN_NEOPIXEL 45

# include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel track(N_REAL, PIN_NEOPIXEL, NEO_RGB + NEO_KHZ800);

int carL, carR;

const byte lLimitPin = 51;
const byte mLimitPin = 49;
const byte rLimitPin = 47;

void setupMech()
{
  pinMode(lLimitPin, OUTPUT);
  pinMode(mLimitPin, OUTPUT);
  pinMode(rLimitPin, OUTPUT);

  track.begin();
  track.setPixelColor(1, 0xffffff);
  track.show(); delay(777);

  carL = 0; carR = N_REAL - WIDTH;
  renderCars();

  track.show();
  delay(777);
}

void renderCars() {
  int carL = (leftStepper.currentPosition() + 50) / 100;
  int carR = N_REAL - (rightStepper.currentPosition() + 50) / 100 - WIDTH;

  for (int ii = 0; ii < WIDTH; ii++) {
    int p = carL + ii;
    if (p >= 0 && p < (N_REAL)) track.setPixelColor(p, track.getPixelColor(p) | 0xff0000);

    p = carR + ii;
    if (p >= 0 && p < (N_REAL)) track.setPixelColor(p, track.getPixelColor(p) | 0x0000ff);
  }
}

void updateMech() {}

// new render mechanism process for steppers
void renderMech()
{
  track.clear();
  renderCars();

  track.show();   // doh!

  int leftP = leftStepper.currentPosition();
  int rightP = rightStepper.currentPosition();

// width 10 of 33 @ 100/ ! yikes  
  bool isLeft = leftP < 5;
  bool isMiddle = ((3300 - rightP) - leftP) <= 2000;
  bool isRight = rightP < 5;

//  runProxies
  digitalWrite(lLimitPin, isLeft ? HIGH : LOW);
  digitalWrite(mLimitPin, isMiddle ? HIGH : LOW);
  digitalWrite(rLimitPin, isRight ? HIGH : LOW);
}

void report()
{
  Serial.print("  left car "); Serial.print(leftStepper.currentPosition());
  Serial.print("     right car "); Serial.print(rightStepper.currentPosition());
  Serial.println("");

  delay(777);
}
<<L2L
A4988
A4988
<<R2L
L2R>>
R2R>>
FULL STOP
STATS