#include <EEPROM.h>

// Stepper driver pins
#define DIR  6
#define STP  7

// 3 push buttons
#define RUN    11
#define CAL    12
#define STR    4

// Switch
#define HOME   9

// 2 LEDs
#define RunLed  10
#define CalLed  5

#define stepsPerRev  200   // How many steps per rev
#define retSpeed     1500  // Speed of the motor retracting in run mode (Lower = Faster)
#define extSpeed     2000  // Speed of the motor extending in run mode (Lower = Faster)
#define minCalSpeed  5000  // Minimum speed in calibration mode
#define maxCalSpeed  2000  // Maximum speed in calibration mode

#define Retracted  1
#define Extended   2

unsigned long retractedSteps = 50;   // Steps taken in reversev after hitting the home switch
unsigned long extendedSteps = 1500;  // Take this many steps to reach extended position (Default), unused if reading from memory
unsigned long currPosition = 0;
unsigned long lastButtonPress = 0;

int retDir = LOW;   // Direction of motor when retracting (Change according to requirement LOW or HIGH)
int extDir = HIGH;  // Direction of motor when extending (Change according to requirement LOW or HIGH)
int Position = 0;
int maxReading = 550;
int minReading = 490;

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

  pinMode(DIR, OUTPUT);
  pinMode(STP, OUTPUT);

  pinMode(RUN, INPUT_PULLUP);
  pinMode(CAL, INPUT_PULLUP);
  pinMode(STR, INPUT_PULLUP);
  pinMode(HOME, INPUT_PULLUP);

  pinMode(RunLed, OUTPUT);
  pinMode(CalLed, OUTPUT);

  Serial.println("Initializing...");
  retractMotor();  // Retract motor at start

  // Clear the EEPROM for the first time, after uploading and ruuning this code once comment out the below 4 lines:
  for (int i = 0 ; i < EEPROM.length() ; i++) {
    EEPROM.write(i, 0);
  }
  delay(1000);
  //------------------------- END

  extendedSteps = readLongFromEEPROM(10);  // Read the last value saved in the Arduino memory
  Serial.print("Extended steps: ");        // Print the value read on serial monitor
  Serial.println(extendedSteps);
}

void loop() {
  if (digitalRead(RUN) == LOW) {  // If run button is pressed
    digitalWrite(RunLed, LOW);  // ON

    if (millis() - lastButtonPress > 50) {
      Serial.println("Run Button pressed!");
      // Toggle the motor position
      if (Position == Extended) {  // If extended
        retractMotor();  // Retract
      }
      else {  // If retracted
        extendMotor();  // Extend
      }
    }
    // Remember last button press event
    lastButtonPress = millis();
  }

  if (digitalRead(CAL) == LOW) {  // If calibrate button is pressed
    digitalWrite(CalLed, LOW);  // ON

    if (millis() - lastButtonPress > 50) {
      Serial.println("Calibrate Button pressed!");
      // Run the calibrate function
      calibratePosition();
    }
    // Remember last button press event
    lastButtonPress = millis();
  }

  digitalWrite(RunLed, HIGH);  // OFF
  digitalWrite(CalLed, HIGH);  // OFF
}

void retractMotor() {  // Function to retract the motor
  Serial.println("Retracting Motor.");
  digitalWrite(DIR, retDir);  // Set the motor direction to retracted
  while (digitalRead(HOME) == HIGH) {  // While switch not pressed
    // Send pulses
    digitalWrite(STP, HIGH);
    delayMicroseconds(retSpeed);
    digitalWrite(STP, LOW);
    delayMicroseconds(retSpeed);
  }

  delay(1000); // Slight delay

  digitalWrite(DIR, extDir);  // Set the motor direction to extended
  for (int i = 0; i < retractedSteps; i++) {  // Take 50 steps away from the home switch
    // Send pulses
    digitalWrite(STP, HIGH);
    delayMicroseconds(retSpeed);
    digitalWrite(STP, LOW);
    delayMicroseconds(retSpeed);
  }
  Serial.println("Motor at home position.");
  Position = Retracted;
  currPosition = retractedSteps;
}

void extendMotor() {  // Function to extend the motor
  Serial.println("Extending Motor.");
  digitalWrite(DIR, extDir);  // Set the motor direction to extended
  for (int i = 0; i < extendedSteps; i++) {  // Take the required steps away from the home position
    // Send pulses
    digitalWrite(STP, HIGH);
    delayMicroseconds(extSpeed);
    digitalWrite(STP, LOW);
    delayMicroseconds(extSpeed);
  }
  Serial.println("Motor at extended position.");
  Position = Extended;
  currPosition = extendedSteps;
}

void calibratePosition() {  // Function to calibrate the motor position
  int motorSpeed = 0;
  while (1) {
    int joystickReading = analogRead(A0);

    if (joystickReading > maxReading) {  // Retract
      motorSpeed = map(joystickReading, 550, 1000, minCalSpeed, maxCalSpeed);
      //motorSpeed = constrain(motorSpeed, minCalSpeed, maxCalSpeed);
      Serial.println(motorSpeed);
      digitalWrite(DIR, retDir);  // Set the motor direction to retracted
      if (digitalRead(HOME) == HIGH) {  // If home buttton not pressed
        // Send pulses
        digitalWrite(STP, HIGH);
        delayMicroseconds(motorSpeed);
        digitalWrite(STP, LOW);
        delayMicroseconds(motorSpeed);
        currPosition--;
      }
    }
    else if (joystickReading < minReading) {  // Extend
      motorSpeed = map(joystickReading, 490, 50, minCalSpeed, maxCalSpeed);
      //motorSpeed = constrain(motorSpeed, minCalSpeed, maxCalSpeed);
      Serial.println(motorSpeed);
      digitalWrite(DIR, extDir);  // Set the motor direction to extended
      // Send pulses
      digitalWrite(STP, HIGH);
      delayMicroseconds(motorSpeed);
      digitalWrite(STP, LOW);
      delayMicroseconds(motorSpeed);
      currPosition++;
    }

    if (digitalRead(STR) == LOW) {  // If store Button pressed
      if (currPosition > retractedSteps) {  // Min extended position should be more than home position
        Serial.println("New extended position stored.");
        extendedSteps = currPosition;
        Position = Extended;
        writeLongIntoEEPROM(10, extendedSteps);  // Save new value on position 10 and onwards
        Serial.print("New extended steps: ");
        Serial.println(extendedSteps);
        for (int i = 0; i < 3; i++) {  // Flash the calibrate LED for 3 sec when new position stored
          digitalWrite(CalLed, LOW);
          delay(500);
          digitalWrite(CalLed, HIGH);
          delay(500);
        }
        break;  // Break out of the calibrate function
      }
      else {
        Serial.println("Invalid position.");
      }
    }
  }
}

void writeLongIntoEEPROM(int address, long number) {  // Function to save long value in eeprom
  EEPROM.write(address, (number >> 24) & 0xFF);
  EEPROM.write(address + 1, (number >> 16) & 0xFF);
  EEPROM.write(address + 2, (number >> 8) & 0xFF);
  EEPROM.write(address + 3, number & 0xFF);
}

long readLongFromEEPROM(int address) {  // Function to read long value from eeprom
  return ((long)EEPROM.read(address) << 24) +
         ((long)EEPROM.read(address + 1) << 16) +
         ((long)EEPROM.read(address + 2) << 8) +
         (long)EEPROM.read(address + 3);
}
A4988