// https://wokwi.com/projects/378031652666068993
// https://forum.arduino.cc/t/sliding-game-opener-project/1143078

const int gateOpenPin = 10;         // GPIO2 for gate open
const int gateClosePin = 3;        // GPIO3 for gate close
const int openProximityPin = 19;    // GPIO4 for open proximity sensor
const int closeProximityPin = 2;   // GPIO5 for close proximity sensor
const uint8_t motorControlPinR = 7;    // GPIO6 for right motor control (PWM)
const uint8_t motorControlPinL = 6;    // GPIO7 for left motor control (PWM)
const int keyPadPin = 9;           // GPIO8 for keypad signal

//Varibales for checking pin states
int gateOpen;
int gateClosed;
int openProximity;
int closeProximity;
int keyPad;

bool keyEvent;    // set on button press

// Variables used for motor speed control
const int maxMotorSpeed = 128;        // 50% PWM speed (0-255)
int currentMotorSpeed = 0;
int openChannel = 1;
int closeChannel = 2;

enum motorStates {TO_OPEN, OFF, TO_CLOSE, NONE};    // should we be closing, off or opening with da motor(s)
unsigned char motors;               // the output variable from the P phase

// Variables used for timing

unsigned long now;
unsigned long startTime = 0;
const unsigned long gateOpenDuration = 7777; // 45 seconds someday

//Variables for gate states
enum gateStates {
  OPEN,
  WAITING_OPEN,
  CLOSED,
  OPENING,
  CLOSING,
  JAMMED_OPENING,
  JAMMED_CLOSING,
  UNKNOWN,
  NO_STATE,
} state;

#define IDNAME(name) #name
const char* stateNames[] = {
  IDNAME(OPEN), 
  IDNAME(WAITING_OPEN), 
  IDNAME(CLOSED),
  IDNAME(OPENING), 
  IDNAME(CLOSING), 
  IDNAME(JAMMED_OPENING), 
  IDNAME(JAMMED_CLOSING), 
  IDNAME(UNKNOWN),
  IDNAME(NO_STATE),
};

enum gateStates gateState;
// enum gateStates prevGateState;

void setup() {
  // Initialize serial communication
  Serial.begin(115200);

  setupSimulatedDoor();
  runSimulatedDoor();

  // Initialize pins
  pinMode(gateOpenPin, INPUT_PULLUP);
  pinMode(gateClosePin, INPUT_PULLUP);
  pinMode(openProximityPin, INPUT_PULLUP);
  pinMode(closeProximityPin, INPUT_PULLUP);
  pinMode(keyPadPin, INPUT_PULLUP);
  ledcAttachPin(motorControlPinL, closeChannel);
  ledcAttachPin(motorControlPinR, openChannel);
  ledcSetup(closeChannel, 1000, 8);
  ledcSetup(openChannel, 1000, 8);
  ledcWrite(closeChannel, 0);

  //Get the current state of the various sensors
  keyPad = digitalRead(keyPadPin);
  gateOpen = digitalRead(gateOpenPin);
  gateClosed = digitalRead(gateClosePin);
  openProximity = digitalRead(openProximityPin);
  closeProximity = digitalRead(closeProximityPin);

  // determine the initial gate position
  if (gateOpen == LOW && gateClosed == LOW) {
    Serial.println("ERROR - both limit switches activated");

    while (1) {
// whatever.
    }
  }

  if (gateOpen == HIGH && gateClosed == HIGH) {
    gateState = UNKNOWN;
    Serial.println("GATE POSITION UNKNOWN");
  }

  if (gateOpen == LOW && gateClosed == HIGH) {
    Serial.println("GATE OPEN CHECK");
    gateState = OPENING;
  }

  if (gateOpen == HIGH && gateClosed == LOW) {
    Serial.println("GATE CLOSED CHECK");
    gateState = CLOSING;
  }
}

// compensate for active LOW or active HIGH sensor signals
# define GATE_SENSE		LOW
# define PROX_SENSE		LOW
# define BUTTON_SENSE	LOW

void loop() {
  static unsigned long lastTime;
 
  now = millis();

  runSimulatedDoor();

/* Get the current state of the various sensors
  
  keyPad true if button is being pressed
  gateOpen true if the gate open limit has been reached
  gateClose true if the gate close limit has been reached 
  openProximity true if something is blockage the opening movement
  closeProximity true if something is blockage the closing movement
*/

// INPUT

  keyPad = digitalRead(keyPadPin) == BUTTON_SENSE;
  gateOpen = digitalRead(gateOpenPin) == GATE_SENSE;
  gateClosed = digitalRead(gateClosePin) == GATE_SENSE;
  openProximity = digitalRead(openProximityPin) == PROX_SENSE;
  closeProximity = digitalRead(closeProximityPin) == PROX_SENSE;

// PROCESS - keypad
  if (keyEvent)
    Serial.println("         ignored that!");

  keyEvent = false;  // unless the button got pressed:
  getSignal();

// PROCESS limit and jam switches
  static unsigned char lastMotors = NONE;
  motors = OFF; // unless told otherwise by the process phase
  gateFSM();    // process the inputs, develop motors control variable

// OUTPUT - set the motor(s) to the task
//  Serial.println("now set the motor(s) opening, closing or off");
// if you need to, probably really should always in real life but
// we can cut the spam to printing only when a change is made

  if (motors != lastMotors) {
    lastMotors = motors;

    if (motors == TO_OPEN) {
      Serial.println("making the motor(s) opening the gate");
      motorControlO();
    }
    
    if (motors == TO_CLOSE) {
      Serial.println("making the motor(s) closing the gate");
      motorControlC();
    }

    if (motors == OFF) {
      Serial.println("stop the motor(s)");
      motorsOff();
    }
  }

  delay(50);    // poor man's loop throttle, debounce and spam mitigation
}

void getSignal() {
  static bool lastKeyPad;
  if (keyPad && !lastKeyPad) {
    Serial.println("I see you pressed the button.");

    keyEvent = true;
  }
  lastKeyPad = keyPad;
}

void gateFSM()
{
  static gateStates lastReport;
  if (gateState != lastReport) {
    Serial.print(" in FSM, state is ");
    Serial.println(stateNames[gateState]);
    lastReport = gateState;
  }

  switch (gateState) {
  case UNKNOWN:
    if (keyEvent) {
      gateState = OPENING;

      keyEvent = false;
    }

    break;

  case CLOSED:
    if (keyEvent) {
      gateState = OPENING;

      keyEvent = false;   // handled
    }

    break;

  case OPENING:
    motors = TO_OPEN;
    if (gateOpen) {        
      Serial.println("GATE OPEN");
      motors = OFF;
      gateState = OPEN;
    }
    if (openProximity) {
      Serial.println("OBJECT FOUND WHILE OPENING");
      gateState = JAMMED_OPENING;
    }

    break;

  case JAMMED_OPENING:
    if (!openProximity)  // <- should we wait for clearance or just
      gateState = CLOSING;

    break;

case OPEN:
    motors = OFF;
    startTime = now;
    gateState = WAITING_OPEN;

    break;

  case WAITING_OPEN:
    if (now - startTime >= gateOpenDuration) {
      Serial.println("time's up! Closing gate.");
      gateState = CLOSING;
    }
 
    break;
  case CLOSING:
    motors = TO_CLOSE;
    if (gateClosed) {        
      Serial.println("GATE CLOSED");
      motors = OFF;
      gateState = CLOSED;
    }
    if (closeProximity) {
      Serial.println("OBJECT FOUND WHILE CLOSING");
      gateState = JAMMED_CLOSING;
    }
    
    break;

  case JAMMED_CLOSING:
    if (!closeProximity)  // <- should we wait for clearance or just
      gateState = OPENING;

    break;
  }
}

void motorControlC() 
{
	Serial.println("gate is (still) closing");

  ledcWrite(openChannel, 0);
  ledcWrite(closeChannel, 255);
}

void motorControlO()
{
	Serial.println("gate is (still) opening");

  ledcWrite(openChannel, 255);
  ledcWrite(closeChannel, 0);
}

void motorsOff()
{
  Serial.println("motors off");

  ledcWrite(openChannel, 0);
  ledcWrite(closeChannel, 0);
}


//////////////////////


/*
// library Adafruit_NeoPixel
// setup() +
  setupSimulatedDoor();
  runSimulatedDoor();   // step the machine so inouts and outpus make sense

// void loop() +
  runSimulatedDoor();

*/

// below here finds itself the door simulation machinery
// find five unused I/O pins

# define sDoorDown  0
# define sDoorUp    1
// # define doorX      18

# define sDoorDownMotorPin  4
# define sDoorUpMotorPin    5

# define door

# include <Adafruit_NeoPixel.h>
# define PIN 18          // the pin
# define NPIXELS 17     // number of LEDs on strip

Adafruit_NeoPixel doorBar(NPIXELS, PIN, NEO_GRB + NEO_KHZ800); 

int lPosition = 6;      // sim door starts at 6
unsigned char dir = 1;  // moving up. or is true down?

void setupSimulatedDoor()
{
  pinMode(sDoorDown, OUTPUT);
  pinMode(sDoorUp, OUTPUT);

  pinMode(sDoorDownMotorPin, INPUT);
  pinMode(sDoorUpMotorPin, INPUT);

  doorBar.begin();
  doorBar.setPixelColor(2, 0xff0000);
  doorBar.show();

  delay(777);
}

void runSimulatedDoor()
{
//  int doorPosition = analogRead(A0);
//  delay(20);  
//  digitalWrite(sDoorDown, doorPosition < 100 ? LOW : HIGH); // active low 
//  digitalWrite(sDoorUp, doorPosition > 923 ? LOW : HIGH);
   
  static unsigned long lastTime;
  if (millis() - lastTime < 333) return;
  lastTime = millis();

// this did work checking in this case. may not always
  if (0) {  // test - door will just cycle up and down all by itself
    lPosition += dir ? -1 : 1;
    if ((lPosition == -1) || (lPosition == NPIXELS)) dir = !dir;
  }
  else {    // read the main code outputs and run the door if
    if (digitalRead(sDoorDownMotorPin)) lPosition--;
    if (digitalRead(sDoorUpMotorPin)) lPosition++;
  }

  if (lPosition == -1) lPosition = 0;
  if (lPosition == NPIXELS) lPosition = NPIXELS - 1;

// argh! Serial.println(lPosition); delay(777);

  digitalWrite(sDoorDown, lPosition == 0 ? LOW : HIGH);
  digitalWrite(sDoorUp, lPosition == NPIXELS - 1 ? LOW : HIGH); 

// moved the output section here to keep everything in phase:

  doorBar.clear();
  doorBar.setPixelColor(lPosition, 0xff0000);
  doorBar.show();
}