//---------------Conventions Used------------------------
//STEPPER MOTOR
#define FORWARD 1               //Change this to 0 if the Stepper is moving in wrong direction (default value is 1)
#define BACKWARD !FORWARD
#define RETRACT_STEP -500          //Decrease this value further if the tray is not retracted completely
#define REST_STEP -10             //Increase or decreas this value depending upon offset between true resting position and current resting position of the tray
#define LS_TRIG 0               //When limit switch is trigered either from high to low or from low to high this position is taken as reference
#define EJECT_STEP 500          //Increae this value if the tray is not fully ejected
#define FLOAT_ON_VAL 1          //Change this to 0 if the device turns on the water check led  when the level are okay.
#define PUMP_ON 1               //Change the value to 0 if pump is working incorrectly
#define PUMP_OFF !PUMP_ON

//SERVO MOTOR
#define RETRACT_DUR 3000        // Duration for which servo moves to attain retracted position from resting
#define EJECT_DUR 3000          // Duration fro which servo moves to attain ejecred position from resting
#define SERVO_SPEED 250         // Speed of servo motor
#define SERVO_BASE 1500         // IDLE position of servo
//----------------Pin Declaration------------------------
#define PUMPA_PIN 2             // sets the water pumpA to pin 2 on the arduino
#define PUMPB_PIN 3             // sets the water pumpB to pin 3 on the arduino
#define HEATER_PIN 4             // sets the heater to pin 4
#define BUTTON_A 12             // sets the buttonA input on pin 12
#define BUTTON_B 11             // sets the buttonB input on pin 11
#define WATER_ADD_ERR 9         // Error led for adding water or additive on pin 9
#define ADDITIVE_FLOAT_PIN 8    // Additive float check input on pin 8
#define WATER_FLOAT_PIN 7       // Water float check input on pin 7
#define DIR_PIN 6               // Direction of servo motor on pin 6
#define STEP_PIN 5              // Step of servo motor on pin 5
#define LEDA A1                 // Button A led connects to Arudino pin A1
#define LEDB A2                 // Button B led connects to Arduion pin A2
#define LS_PIN A0               // Limit switch which turns the device on connects to Arduino pin A0
#define LS_TRAY A3              // Limit switch input to provide feedback for Tray Movement
#define SERVO_PIN 13 // Servo motor signal attached to digital pin 13 of Arduino Uno

//------------------TIME DURATION-------------------------
#define PREHEAT_DURATION 500   // Duration for which heater preheats
#define PUMPA_DURATION 1500    // Duration for which pumpA pumps the water
#define PUMPB_DURATION 200     // Duration for which pumpB pumps the additive
#define EJECTION_DELAY 500     // Delay between tray ejection and stoping of pumpA
#define EJECTED_DURATION 6000  // Duration for which the tray stays in ejected form.
#define ERR_BLNIK_RATE 1000     // Blinking rate of water and additive level led, decrease the value to increase blink rate
#define LED_BLINK_DELAY 500     // Delay between blinking of LED A and B
#define STEPPER_DELAY 700       // Increase or decrease this value to change the stepper speed
// Variables for water pump
int PUMPA_STATE = LOW;           // ensures the pump starts in the off position 
int PUMPB_STATE = LOW;
// Variables for heating element
int HEATERSTATE = LOW;        // ensures heater starts in off position


// Initializing Pushbuttons
int buttonStateA = 0;         // variable for reading the pushbutton status
int buttonStateB = 0;

// Stepper Motor Variables:
int stepper_position = LS_TRIG;
int stepper_target = LS_TRIG;
// Initializing Float Switch
int waterFloatStatus = !FLOAT_ON_VAL;
int additiveFloatStatus = !FLOAT_ON_VAL;
// Variable to store the current state:
int current_state = 0;

//Timers to schedule different tasks
uint32_t timer = 0;
uint32_t err_led_timer = 0;
uint32_t stepper_timer = 0;
uint32_t seq_timer = 0;
uint32_t led_timer = 0;

// Include Servo
#include <Servo.h>
Servo servo_motor;                    // create servo object to control a servo
enum position{RETRACTED, RESTING, EJECTED};
enum position servo_position;
enum position req_pos;

int prev_ls_tray_state = digitalRead(LS_TRAY);
uint32_t servo_timer = 0;

// Setting up LCD Screen 
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);   // no need to change any values
int screen = 0;

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

  //Initialize the input pins
  pinMode(BUTTON_A, INPUT_PULLUP);
  pinMode(BUTTON_B, INPUT_PULLUP);
  pinMode(ADDITIVE_FLOAT_PIN, INPUT_PULLUP);
  pinMode(WATER_FLOAT_PIN, INPUT_PULLUP);

  //Initialize the output pins
  pinMode(PUMPA_PIN, OUTPUT);
  pinMode(PUMPB_PIN, OUTPUT);
  pinMode(HEATER_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  pinMode(STEP_PIN, OUTPUT);
  pinMode(WATER_ADD_ERR, OUTPUT);
  pinMode(LEDA, OUTPUT);
  pinMode(LEDB, OUTPUT);
  pinMode(LS_PIN, INPUT);
  pinMode(LS_TRAY, INPUT);
  // initialize lcd screen
  lcd.init();
  // turning on backlight
  lcd.backlight();
  update_lcd(1);
  servo_motor.attach(SERVO_PIN);
  req_pos = RESTING;
  //calibrate();
}

uint32_t loop_timer = micros();

void loop () {
  //Serial.print("Timer: ");Serial.print(micros() - loop_timer);
  loop_timer = micros();
  while ( !digitalRead(LS_PIN) && current_state == 0) {
    digitalWrite(LEDA, LOW);
    digitalWrite(LEDB, LOW);
    delay(1);
  }
  timer = micros();
  readInputs();
  //Serial.print(" 1 Timer: ");Serial.print(micros() - loop_timer);
  check_water_additive_level();
  //Serial.print(" 2Timer: ");Serial.print(micros() - loop_timer);
  whoopy_whipes();
  //Serial.print(" 3Timer: ");Serial.print(micros() - loop_timer);
  write_outputs();
  //Serial.print(" 4Timer: ");Serial.print(micros() - loop_timer);
  blink_leds();
  //Serial.print(" 5Timer: ");Serial.print(micros() - loop_timer);
  delay(200);
}

void readInputs() {
  waterFloatStatus = digitalRead(WATER_FLOAT_PIN);
  additiveFloatStatus = digitalRead(ADDITIVE_FLOAT_PIN);
  buttonStateA = digitalRead(BUTTON_A);
  buttonStateB = digitalRead(BUTTON_B);
  if (current_state == 0 && (waterFloatStatus == FLOAT_ON_VAL || additiveFloatStatus == FLOAT_ON_VAL )) {
    seq_timer = timer;
    if (buttonStateA == 0) {
       current_state = 1;
       update_lcd(2);
    } else {
       if (buttonStateB == 0 ) {
           current_state = 2;
           update_lcd(2);
       }
    }
  }
  //Serial.print("ButtonA:");Serial.print(buttonStateA);
  //Serial.print(" ButtonB:");Serial.print(buttonStateB);
  //Serial.print(" Current State:");Serial.print(current_state);
  //Serial.print(" Float A:");Serial.print(waterFloatStatus);
  //Serial.print(" Float B:");Serial.print(additiveFloatStatus);
}

void write_outputs () {
  digitalWrite(PUMPA_PIN, PUMPA_STATE);
  digitalWrite(PUMPB_PIN, PUMPB_STATE);
  digitalWrite(HEATER_PIN, HEATERSTATE);
  //move_stepper(stepper_target);
  move_servo();
}

void blink_leds () {
  if ( millis() - led_timer > LED_BLINK_DELAY) {
    switch (current_state){
      case 0 :  digitalWrite(LEDA, !digitalRead(LEDA));
                digitalWrite(LEDB, !digitalRead(LEDA));
                break;
      case 1 :  digitalWrite(LEDA, 1);
                digitalWrite(LEDB, 0);
                break;
      case 2 :  digitalWrite(LEDA, 0);
                digitalWrite(LEDB, 1);
                break;
    }
    led_timer = millis();
  }
}


void check_water_additive_level() {
    if (waterFloatStatus != FLOAT_ON_VAL || additiveFloatStatus != FLOAT_ON_VAL ) {
      if (current_state == 0) {
        update_lcd(3);
      }
      if ( timer > (err_led_timer + (uint32_t(ERR_BLNIK_RATE)*1000))) {
        digitalWrite(WATER_ADD_ERR, !digitalRead(WATER_ADD_ERR));
        err_led_timer = timer;
      }
    } else {
      digitalWrite(WATER_ADD_ERR, LOW);
      if (current_state == 0) {
        update_lcd(1);
      }
    }
}

void whoopy_whipes () {
    if (current_state == 1 ) {
        sequence_a();
    } else {
        if (current_state == 2 ) {
            sequence_b();
        }
    }
}

void sequence_a () {
  int tray_position = REST_STEP;
  uint32_t duration = (timer - seq_timer)/1000;
  if ( duration < PREHEAT_DURATION ) { // Preheating and retraction sequence begins here
    HEATERSTATE = HIGH;
    stepper_target = RETRACT_STEP;
    req_pos = RETRACTED;
  } else {
    if (duration < (PREHEAT_DURATION + PUMPA_DURATION) ) { // Pumimp water starts here for 15 seconds
      stepper_target = RETRACT_STEP;
      req_pos = RETRACTED;
      if (servo_position != RETRACTED) {
          seq_timer = timer - (PREHEAT_DURATION*1000);
      } else {
          PUMPA_STATE = PUMP_ON;
      }
    } else {
      stepper_target = RETRACT_STEP;
      req_pos = RETRACTED;
      HEATERSTATE = LOW;
      PUMPA_STATE = PUMP_OFF;
      if ( duration > (EJECTION_DELAY + PREHEAT_DURATION + PUMPA_DURATION) ) { // Tray ejection begins here
        stepper_target = EJECT_STEP;
        req_pos = EJECTED;
        if ( duration > (EJECTED_DURATION + EJECTION_DELAY + PREHEAT_DURATION + PUMPA_DURATION) ) { // Tray retraction starts here
          stepper_target = REST_STEP;
          req_pos = RESTING;
          HEATERSTATE = LOW;
          PUMPA_STATE = PUMP_OFF;
          current_state = 0;
          seq_timer = timer;
          }
        }
     }
  }
  //Serial.print(" Duration:");Serial.print(duration);
}

void sequence_b () {
  int tray_position = REST_STEP;
  uint32_t duration = (timer - seq_timer)/1000;
  if ( duration < PREHEAT_DURATION ) { // Preheating and retraction sequence begins here
    HEATERSTATE = HIGH;
    stepper_target = RETRACT_STEP;
    req_pos = RETRACTED;
  } else {
    if (duration < (PREHEAT_DURATION + PUMPA_DURATION) ) { // Pumimp water starts here for 15 seconds
      stepper_target = RETRACT_STEP;
      req_pos = RETRACTED;
      if ( servo_position != RETRACTED) {
          seq_timer = timer - (PREHEAT_DURATION*1000);
      } else {
          if (duration < PREHEAT_DURATION + PUMPB_DURATION){
              PUMPB_STATE = PUMP_ON;
          } else {
              PUMPB_STATE = PUMP_OFF;
          }
          PUMPA_STATE = PUMP_ON;
      }
    } else {
      stepper_target = RETRACT_STEP;
      req_pos = RETRACTED;
      HEATERSTATE = LOW;
      PUMPA_STATE = PUMP_OFF;
      if ( duration > (EJECTION_DELAY + PREHEAT_DURATION + PUMPA_DURATION) ) { // Tray ejection begins here
        stepper_target = EJECT_STEP;
        req_pos = EJECTED;
        if ( duration > (EJECTED_DURATION + EJECTION_DELAY + PREHEAT_DURATION + PUMPA_DURATION) ) { // Tray retraction starts here
          stepper_target = REST_STEP;
          req_pos = RESTING;
          HEATERSTATE = LOW;
          PUMPA_STATE = PUMP_OFF;
          current_state = 0;
          seq_timer = timer;
          }
        }
     }
  }
  //Serial.print(" Duration:");Serial.print(duration);
}


int take_step (int dir) {
   if ( timer > ( stepper_timer + STEPPER_DELAY)) {
    digitalWrite(DIR_PIN, dir);
    digitalWrite(STEP_PIN, !digitalRead(STEP_PIN));
    stepper_timer = timer;
    return 1;
  }
  return 0;
}

int move_stepper (int required_position ) {
    if (required_position != stepper_position) {
      if (required_position > stepper_position){
        stepper_position = stepper_position + take_step(FORWARD);
      } else {
        stepper_position = stepper_position - take_step(BACKWARD);
      }
      return 0;
    } else {
        digitalWrite(STEP_PIN, LOW);
        return 1;
    }
  //Serial.print(" Required Position:");Serial.print(required_position);
  //Serial.print(" Stepper Position:");Serial.print(stepper_position);
}

void calibrate () {
  int previous_state = digitalRead(LS_TRAY);
  uint32_t debounce_timer = millis();
  while (digitalRead(LS_TRAY) == previous_state) {
    timer = micros();
    if ( previous_state == 0) {
      take_step(BACKWARD);
    } else {
      take_step(FORWARD);
    }
    if (millis() - debounce_timer > 5000 ){
        break;
    }     
  }
}

int move_to_resting() {
  Serial.print("PTRAY: ");
  Serial.print(prev_ls_tray_state);
  Serial.print("\tTRAY: ");
  Serial.print(digitalRead(LS_TRAY));

  if (prev_ls_tray_state == 0) {
    if (prev_ls_tray_state == digitalRead(LS_TRAY)) {
      servo_motor.writeMicroseconds(SERVO_BASE + SERVO_SPEED);
      return 0;
    } else {
      servo_motor.writeMicroseconds(SERVO_BASE);
      return 1;
    }
  } else {
    if (prev_ls_tray_state == digitalRead(LS_TRAY)) {
      servo_motor.writeMicroseconds(SERVO_BASE - SERVO_SPEED);
      return 0;
    } else {
      servo_motor.writeMicroseconds(SERVO_BASE);
      return 1;
    }
  }
}

void move_servo() {
  int t_req_pos = req_pos;
  if (req_pos != servo_position) {
    Serial.println();
    if (servo_position == EJECTED) {
      if (req_pos == RETRACTED) {
        t_req_pos = RESTING;
        servo_timer = millis();
      }
    } else if (servo_position == RETRACTED) {
      if (req_pos == EJECTED) {
        t_req_pos = RESTING;
        servo_timer = millis();
      }
    }
    switch (t_req_pos) {
    case RETRACTED:
      if (millis() - servo_timer < RETRACT_DUR) {
        Serial.print("\tRETRACTING\t");
        servo_motor.writeMicroseconds(SERVO_BASE - SERVO_SPEED);
      } else {
        Serial.print("\tRETRACTED<>\t");
        servo_position = RETRACTED;
        servo_motor.writeMicroseconds(SERVO_BASE);
        servo_timer = millis();
      }
      break;

    case RESTING:
      Serial.print("\RESTING\t");
      if (move_to_resting() == 1) {
        servo_position = RESTING;
        servo_motor.writeMicroseconds(SERVO_BASE);
      }
      break;

    case EJECTED:
      if (millis() - servo_timer < EJECT_DUR) {
        Serial.print("\tEJECTING\t");
        servo_motor.writeMicroseconds(SERVO_BASE + SERVO_SPEED);
      } else {
        Serial.print("\EJECTED\t");
        servo_position = EJECTED;
        servo_motor.writeMicroseconds(SERVO_BASE);
        servo_timer = millis();
      }
      break;
    }
  } else {
    servo_timer = millis();
    prev_ls_tray_state = digitalRead(LS_TRAY);
    servo_motor.writeMicroseconds(SERVO_BASE);
  }
}

void update_lcd (int target_screen) {
  if ( screen != target_screen ) {
    Serial.print("LCD is printing");
    if ( target_screen == 1 ) {
      lcd.setCursor(0,0);
      lcd.println("Ready to Launch:");
      lcd.setCursor(0,1);
      lcd.println(" Press a Button ");
    }
    if (target_screen == 2 ) {
      lcd.setCursor(0,0);
      lcd.println("  Running Cycle ");
      lcd.setCursor(0,1);
      lcd.println("  Please Wait   ");
    }
    if (target_screen == 3 ) {
      lcd.setCursor(0,0);
      lcd.println("     Error:     ");
      lcd.setCursor(0,1);
      lcd.println("Refill Fluid Tank");
    }
    if (target_screen == 4 ) {
      lcd.setCursor(0,0);
      lcd.println(" No More Wipes  ");
      lcd.setCursor(0,1);
      lcd.println(" Press Button 3 ");
    }
    screen = target_screen;
  }
}

A4988