// You can change following variables inside code
// DEBUG line 13 if you want to use Serial monitor set it 1 or else 0 to stop serial pints
// encoderStep line 24 to set the steps to start motor for a cut
// allowedFluctuation line 26 to set the allowed fluctuation in percentage (0.0 to 1.0 for 100 percent)
// relayLogic line 57 to set the working logic of your relay HIGH/LOW
// forwardDistance line 64 to set forward cut steps for motor
// backwardDistance line 65 to set Reverse cut steps for motor it will be negative

// Including Required Libraries
#include <AccelStepper.h>  // You need to install this library from Library manager. Writer "AccelStepper" in search
#include <Encoder.h>       // You need to install this library from Library manager. Writer "Encoder" in search
#include "Button2.h"

#define DEBUG 1  // Set Debug level 0-1 to prints result on serial monitor (Set it to 0 when you don't need serial prints)

#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif

// =========== Encoder Adjustment =============
int encoderStep = 40;  // Number of encoder pulses to trigger movement this will be base value

float allowedFluctuation = 0.2;  // Maximum allowed fluctuation from base value +/- in percentage allowed value 0.0 - 1.0 (0% to 100%)

int totalEncoderSteps = 0;  // New value for encoder steps to trigger the motor

// Moving Average Settings
const int numReadings = 10;  // Number of readings to take average
int readings[numReadings];   // Array to store readings
int readIndex = 0;           // Index of the current reading
int total = 0;               // Running total of the readings
int average = 0;             // The average of the readings

// Stepper Speed
int maxSpeed = 1000;            // Set motor maximum speed here
float speedModifier = 1.0;      // Adjust speed modifier with respect to maximum speed (use 0.0 - 1.0)
float homeSpeedModifier = 0.4;  // Home speed modifier to take stepper to home (it's btter to keep it low)

// Creating Buttons instance
Button2 resetButton;
Button2 relay1Btn;
Button2 relay2Btn;
Button2 potCheckBtn;

// Pins for Inputs
const int estopPin = 2;         // Emergency Stop Button
const int stepperAlarmPin = 3;  // Stepper Motor Alarm Pin
const int encoderPinA = 4;      // Rotary Encoder Pin A
const int encoderPinB = 5;      // Rotary Encoder Pin B
const int limitSwitchPin = 6;   // Limit Switch (NPN Proximity)
const int resetPin = 7;         // Reset Button
const int potCheckPin = A0;     // Potentiometer Check Button
const int linearPot = A1;       // Linear Potentiometer for relation with steps

// Create a Rotary Encoder Object
Encoder rotaryEncoder(encoderPinA, encoderPinB);
long newPosition = 0;  // variable to keep track of encoder values
// Pins for stepper Driver
const int stepPin = 8;  // Step pin for stepper driver
const int dirPin = 9;   // Direction pin for stepper driver
const int enPin = 10;   // Enable pin for stepper driver


// Stepper Motor Object (Using AccelStepper for smoother control)
AccelStepper stepper(AccelStepper::DRIVER, stepPin, dirPin);

// Pins for Outputs
const int relay1Pin = 11;     // Relay 1
const int relay2Pin = 12;     // Relay 2
const int relay1BtnPin = A2;  // Relay 1 button
const int relay2BtnPin = A3;  // Relay 2 button

bool relayLogic = LOW;  // Relay working Logic to operate Relay (Set it as HIGH/LOW based on your relay)

volatile bool emergencyStop = false;  // flag to be set on emergency button press

// Constants
const int forwardDistance = 1000;    // Pulses to move forward (adjust for your setup)
const int backwardDistance = -1000;  // Pulses to move back

// Global Variables
long encoderPosition = 0;
bool forwardCut = false;
void emergencyBtnPressed() {
  emergencyStop = true;  // set emergency flag true
}
void setup() {
  Serial.begin(9600);
  // Initialize pins
  pinMode(estopPin, INPUT_PULLUP);
  pinMode(limitSwitchPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(estopPin), emergencyBtnPressed, FALLING);

  // Stepper Driver Alarm Pin

  pinMode(stepperAlarmPin, INPUT_PULLUP);
  // Falling means interrup when pin goes low from high state
  // Rising means interrup when pin goes high from low state

  attachInterrupt(digitalPinToInterrupt(stepperAlarmPin), emergencyBtnPressed, FALLING);  // You can choose FALLING/RiSING as last argument.



  resetButton.begin(resetPin, INPUT_PULLUP, true);     // Initialize button for reset
  relay1Btn.begin(relay1BtnPin, INPUT_PULLUP, true);   // Initialize button for Relay 1
  relay2Btn.begin(relay2BtnPin, INPUT_PULLUP, true);   // Initialize button for Relay 2
  potCheckBtn.begin(potCheckPin, INPUT_PULLUP, true);  // Initialize button for Potentiometer Check

  // Attaching events to button press
  resetButton.setPressedHandler(resetBtnFunc);
  relay1Btn.setPressedHandler(relay1BtnFunc);
  relay2Btn.setPressedHandler(relay2BtnFunc);
  potCheckBtn.setPressedHandler(potCheckBtnFunc);
  pinMode(relay1Pin, OUTPUT);
  pinMode(relay2Pin, OUTPUT);
  pinMode(enPin, OUTPUT);

  digitalWrite(relay1Pin, !relayLogic);  // Initially turn off relay
  digitalWrite(relay2Pin, !relayLogic);  // Initially turn off relay

  // Set up stepper motor parameters
  stepper.setMaxSpeed(maxSpeed);
  stepper.setAcceleration(500);

  stopAll();  // Initiall stop everything
  // Homing routine on startup
  homeStepper();

  // Initialize encoder position
  encoderPosition = rotaryEncoder.read();
  calculateNewEncoderSteps();  // calculate current pot steps
}

void loop() {
  // Check for emergency stop
  if (emergencyStop) {
    // Stop everything and reset homing
    stopAll();
    // Reset button functionality
    while (digitalRead(resetPin) != LOW) {
      ;
    }
    homeStepper();
    emergencyStop = false;  // Reset emergency stop flag
  }

  newPosition = rotaryEncoder.read();  // Read the rotary encoder position

  resetButton.loop();                  // Continoutsly check if button is pressed
  relay1Btn.loop();                    // Continoutsly check if button is pressed
  newPosition = rotaryEncoder.read();  // Read the rotary encoder position
  relay2Btn.loop();                    // Continoutsly check if button is pressed
  potCheckBtn.loop();                  // Continoutsly check if button is pressed

  // Read the rotary encoder position
  newPosition = rotaryEncoder.read();

  // Check if the encoder has reached the set threshold for movement
  if (abs(newPosition - encoderPosition) >= totalEncoderSteps) {
    debug("Stepper was Triggered with encoder steps:");
    debugln(abs(newPosition - encoderPosition));
    debug("Encoder Value:");
    debugln(rotaryEncoder.read());
    if (forwardCut == false) {  // Check if motor has made a backward cut previously then make forward cut now
      // Move the stepper motor forward
      moveStepper(forwardDistance);
      forwardCut = true;  // Set the forward cut flag
    } else {
      // Move the stepper motor back
      moveStepper(backwardDistance);
      forwardCut = false;  // Reset forward cut flag
    }
    newPosition = rotaryEncoder.read();  // Read the rotary encoder position
    // Update encoder position
    encoderPosition = newPosition;
  }
  // Read the rotary encoder position
  newPosition = rotaryEncoder.read();
}
void resetBtnFunc(Button2& btn) {
  debugln("reset btn pressed");
  homeStepper();  // Take stepper to home
}
void relay1BtnFunc(Button2& btn) {
  debugln("Relay 1 btn pressed");
  digitalWrite(relay1Pin, !digitalRead(relay1Pin));  // Read current state of relay and reverse it
}
void relay2BtnFunc(Button2& btn) {
  debugln("Relay 2 btn pressed");
  digitalWrite(relay2Pin, !digitalRead(relay2Pin));  // Read current state of relay and reverse it
}
void potCheckBtnFunc(Button2& btn) {
  debugln("Pot Check btn pressed");
  calculateNewEncoderSteps();  // Calculate Encoder steps value based on potentiometer
}
void calculateNewEncoderSteps() {
  allowedFluctuation = constrain(allowedFluctuation, 0.0, 1.0);                                                                                          // Make sure the allowed fluctuation will be in 0.0-1.0 range
  int potMap = map(analogRead(linearPot), 0, 1023, encoderStep - (encoderStep * allowedFluctuation), encoderStep + (encoderStep * allowedFluctuation));  // Map the potentiometer value from -100, 100 (Mid value will be 0)
  if (potMap != totalEncoderSteps) {
    debug("New Encoder Mapped value:");
    debugln(potMap);
    totalEncoderSteps = potMap;  // Get the total steps from mapped values
  }
}

void turnOnRelay1() {
  digitalWrite(relay1Pin, relayLogic);  // Turn on relay 1
}
void turnOffRelay1() {
  digitalWrite(relay1Pin, !relayLogic);  // Turn off relay 1
}
void turnOnRelay2() {
  digitalWrite(relay2Pin, relayLogic);  // Turn on relay 2
}
void turnOffRelay2() {
  digitalWrite(relay2Pin, !relayLogic);  // Turn off relay 2
}
// Function to move the stepper motor
void moveStepper(int distance) {
  digitalWrite(enPin, LOW);                            // Enable stepper driver
  speedModifier = constrain(speedModifier, 0.1, 1.0);  // Don't allow user to go beyond 0.1 to 1.0 range
  int dirValue = 1;
  if (distance < 0)
    dirValue = -1;
  stepper.moveTo(distance);
  while (stepper.distanceToGo() != 0) {
    if (emergencyStop) {  // if emergency button is pressed get out  of loop
      return;
    }
    stepper.setSpeed(dirValue * maxSpeed * speedModifier);
    stepper.runSpeed();
  }
  stepper.setCurrentPosition(0);

  digitalWrite(enPin, HIGH);  // Disable stepper driver
}

// Function to home the stepper motor
void homeStepper() {
  // Move the stepper motor backward slowly until the limit switch is triggered
  digitalWrite(enPin, LOW);  // Enable stepper driver
  // stepper.setSpeed(-200 * speedModifier);    // Slow speed for homing
  homeSpeedModifier = constrain(homeSpeedModifier, 0.1, 1.0);  // Don't allow user to go beyond 0.1 to 1.0 range
  while (digitalRead(limitSwitchPin) != LOW) {
    if (emergencyStop) {  // if emergency button is pressed get out  of loop
      return;
    }
    stepper.setSpeed(-maxSpeed * homeSpeedModifier);  // Slow speed for homing

    stepper.runSpeed();
  }
  // Stop the motor and set position to 0
  stepper.setCurrentPosition(0);
  digitalWrite(enPin, HIGH);  // Disable stepper driver
  forwardCut = false;         // Reset forward cut flag
}

// Function to stop all actions (emergency stop)
void stopAll() {
  stepper.stop();
  digitalWrite(enPin, HIGH);             // Disable stepper driver
  digitalWrite(relay1Pin, !relayLogic);  // Turf off relay
  digitalWrite(relay2Pin, !relayLogic);  // Turn off relay
}
void customDelay(unsigned long waitTime) {
  unsigned long cTime = millis();  // Note current time
  while ((millis() - cTime) < waitTime) {
    newPosition = rotaryEncoder.read();  // Keep reading encoder
  }
}
A4988