/**********************************
 * Annealing Circuit              *
 * By SMO 07/25/2018              *
 **********************************/
#include <Button.h> //note that this is a different version than the standard button help, it can be found at https://github.com/slavianp/improc/tree/master/Arduino/src/Library
#include <TicksPerSecond.h> // this can be found at https://github.com/slavianp/improc/tree/master/Arduino/src/Library
#include <RotaryEncoderAcelleration.h> // this can be found at https://github.com/slavianp/improc/tree/master/Arduino/src/Library
#include <Metro.h>
#include <elapsedMillis.h>
#include <LiquidCrystal_I2C.h>
//#include <Servo.h>
#include <EEPROM.h>
#include <PinChangeInterrupt.h>
#include <PID_v1.h>
#include <PWM.h>
// Assign I/O pins (Traffic Cop)
#define Encoder_A_Pin 2
#define Encoder_B_Pin 3
#define Encoder_Button_Pin 4
#define Encoder_LED_BLU_Pin 11
#define Encoder_LED_GRN_Pin 12
#define Encoder_LED_RED_Pin 13  // Note that LED is also on pin 13, avoid using as input except UNO board with buffer to LED
#define Case_Sensor_Pin 5
#define Relay_Output_Pin 6
#define Solenoid_Pin 7
//#define Drop_Servo_Pin 7
#define Flow_Sensor_Pin 8
#define Fan_PWM_Pin 9 // analogwrite pins on ATmega328p can only be on 3,5,6,9,10&11
#define Fan_RPM_Pin 10
#define Analog_Amps_Pin A0
#define Analog_Volt_Pin A1
#define Analog_Temp1_Pin A2
#define Analog_Temp2_Pin A3
// Thermistor Values
#define Thermistor_Nom     10000 //Resistance at 25 DegC
#define Thermistor_Temp    25    //Nominal temperature in DegC
#define Thermistor_Beta    3435  //Beta coefficient for thermistor
#define Thermistor_Res     10000 //Value of resistor in series with thermistor
// Initialize I/O values
boolean Relay_State = LOW;
boolean Pushbutton_State = LOW;
boolean Case_Sensor_State = LOW;
boolean Solenoid_State = LOW;
float Analog_Pot_Val = 0;
float Analog_Volt_Val = 0;
float Analog_Amps_Val = 0;
float Max_Amps_Val = 0;
float Analog_Temp1_Val = 0;
float Temp1_Steinhart_Val = 0;
float Analog_Temp2_Val = 0;
float Temp2_Steinhart_Val = 0;
// Initialize other values
int Seq_Step_Num = 1;
int Timer_Setpt = 0;
int Timer_Display = 0;
int Load_Delay_Setpt = 0500; //Delay time in mSEC, gives time for a brass to get into position for annealing
int Drop_Delay_Setpt = 0010; //Delay time in mSEC, gives time for the servo to get into drop position
int Case_Delay_Setpt = 0500; //Delay time in mSEC, gives time for brass to drop and clear the servo
int Home_Delay_Setpt = 0010; //Delay time in mSEC, gives time for the servo to get into home position
int Drop_Pos_Setpt = 90; //in degrees
int Home_Pos_Setpt = 0; //in degrees
volatile long Flow_Total = 0;
volatile long Flow_Interlock_Total = 0;
int Flow_Interlock_Val = 0;
float Flow_Rate = 0;
volatile boolean Button_Toggle_State = 0;
float Joules_Total = 0;
volatile long Fan_RPM_Total = 0;
int Fan_RPM_Rate = 0;
boolean Encoder_Inc = LOW;
// Set Memory location for last time setpoint, setpoint will be saved when power is down
int eeAddress = 0;
float eeData = 0;
// Setup PID for Fan Control
double PID_SP=1000, PID_PV, PID_OP, PID_Kp=2, PID_Ki=0.1, PID_Kd=.5; //Setpoint is multiplied by 10, example 950=95.0 degF
PID FanPID(&PID_PV, &PID_OP, &PID_SP, PID_Kp, PID_Ki, PID_Kd, REVERSE);
// Sequence Descriptions
char *Seq_Step_Desc[] = {
// 12345678901234567892  20 characters max
  "Step 0; not used    ",
  "Step 1; press start ",
  "Step 2; load case   ",
  "Step 3; load delay  ",
  "Step 4; start anneal",
  "Step 5; time anneal ",
  "Step 6; stop anneal ",
  "Step 7; drop pos    ",
  "Step 8; drop delay  ",
  "Step 9; case clear? ",
  "Step10; clear delay ",
  "Step11; home pos    ",
  "Step12; home delay  ",
  "Step13; Return      ",
  "Step14; Check Flow  ",
  "Step15; HIGH AMPS   "
};
// Set the LCD address and 20x4 display also define extra characters
LiquidCrystal_I2C lcd(0x20, 20, 4);
char Print_Buffer[40] = "";
#if defined(ARDUINO) && ARDUINO >= 100
#define printByte(args)  write(args);
#else
#define printByte(args)  print(args,BYTE);
#endif
uint8_t Up_Arrow[8] = {0x04, 0x0E, 0x15, 0x04, 0x04, 0x04, 0x04, 0x00}; //Define Up Arrow for LCD
uint8_t Dn_Arrow[8] = {0x00, 0x04, 0x04, 0x04, 0x04, 0x15, 0x0E, 0x04}; //Define Down Arrow for LCD
//Configure Metro Objects
Metro LCD_Print_Data = Metro(10);
Metro Analog_Inputs_Read = Metro(10);
Metro Flow_Calculation = Metro(10000);
Metro Flow_Interlock = Metro(1000);
Metro Joules_Calculation = Metro(100);
Metro Fan_RPM_Calculation = Metro(6000);
//Configure Timers
elapsedMillis Timer_Actual;
elapsedMillis Button_Debounce;
//Configure Servos
//Servo Drop_Servo;
//Configure Encoder
RotaryEncoderAcelleration Encoder_Knob;
void UpdateEncoder(){
  Encoder_Knob.update();
}
/***************************
 * Setup Program Begin     *
 ***************************/
void setup() {
  //Assign pin modes
  pinMode(Relay_Output_Pin, OUTPUT);
  pinMode(Solenoid_Pin, OUTPUT);
  pinMode(Fan_PWM_Pin, OUTPUT);
  pinMode(Fan_RPM_Pin, INPUT_PULLUP);
  pinMode(Case_Sensor_Pin, INPUT_PULLUP);
  pinMode(Flow_Sensor_Pin, INPUT_PULLUP);
  pinMode(Encoder_Button_Pin, INPUT);
  pinMode(Encoder_LED_BLU_Pin, OUTPUT);
  pinMode(Encoder_LED_GRN_Pin, OUTPUT);
  pinMode(Encoder_LED_RED_Pin, OUTPUT);
  //Get last setpoint before power down to set initial encoder setpoint
  EEPROM.get(eeAddress, eeData);
  //Initialize Encoder
  Encoder_Knob.initialize(Encoder_A_Pin, Encoder_B_Pin);
  Encoder_Knob.setMinMax(50, 2000);
  Encoder_Knob.setPosition(eeData); //Initialize Encoder Position with saved data
  attachInterrupt(0, UpdateEncoder, CHANGE);
 
  //Initialize LCD
  lcd.begin();
  lcd.backlight();
  lcd.createChar(0, Up_Arrow);
  lcd.createChar(1, Dn_Arrow);
  //Set initial postion for Drop Servo
  /*
  Drop_Servo.attach(Drop_Servo_Pin);
  Drop_Servo.write(Drop_Pos_Setpt);
  delay(Drop_Delay_Setpt);
  Drop_Servo.write(Home_Pos_Setpt);
  delay(Drop_Delay_Setpt);
  */
  //Setup PID
  FanPID.SetOutputLimits(100, 255);
  FanPID.SetMode(AUTOMATIC);
  //Setup PWM frequency
  InitTimersSafe(); //Initialize all timers except for 0, to save time keeping functions
  SetPinFrequencySafe(Fan_PWM_Pin, 25000); //Set frequency to 25khz
 
  //Set the flow meter pin change interrupt
  attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(Flow_Sensor_Pin), Flow_Rising, RISING);
  //Sense positive transition of button interrupt
  attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(Encoder_Button_Pin), Push_Button_Toggle, RISING);
  //Set the fan RPM pin change interrupt
  attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(Fan_RPM_Pin), Fan_RPM_Rising, RISING);
}
/***************************
 * Main Program Loop Begin *
 ***************************/
void loop() {
  //Write Digital Outputs
  digitalWrite(Relay_Output_Pin, Relay_State);
  digitalWrite(Solenoid_Pin, Solenoid_State);
  // Read Digital Inputs
  // Pushbutton_State = !digitalRead(Pushbutton_Pin);  //this button is normally closed
  Pushbutton_State = digitalRead(Encoder_Button_Pin);  //this button is normally open
  Case_Sensor_State = digitalRead(Case_Sensor_Pin); //This sensor is normally open
  // Read Analog & Do PID's
  if(Analog_Inputs_Read.check() == 1){
    //To smooth analog signals use a single Pole Digital Filter [out=wt*out+(1-wt)*new]
    Analog_Amps_Val = 0.50 * Analog_Amps_Val + 0.50 * ((analogRead(Analog_Amps_Pin) / 1023.0 * 5000.0 - 5000.0/2.0)/100.0);
    Analog_Volt_Val = 0.75 * Analog_Volt_Val + 0.25 * (analogRead(Analog_Volt_Pin) * 0.079101); // span is 80 volts or 80/1023=0.078201 adjust as needed for accuracy
    Analog_Temp1_Val = 0.95 * Analog_Temp1_Val + 0.05 * (10000.0 / ((1023.0 / analogRead(Analog_Temp1_Pin)) - 1));
    Temp1_Steinhart_Val = Analog_Temp1_Val / Thermistor_Nom;
    Temp1_Steinhart_Val = log(Temp1_Steinhart_Val);
    Temp1_Steinhart_Val /= Thermistor_Beta;
    Temp1_Steinhart_Val += 1.0 / (Thermistor_Temp + 273.15);
    Temp1_Steinhart_Val = 1.0 / Temp1_Steinhart_Val;
    Temp1_Steinhart_Val -= 273.15;
    Temp1_Steinhart_Val = Temp1_Steinhart_Val * 1.8 + 32.0;
    PID_PV = Temp1_Steinhart_Val * 10.0;
    FanPID.Compute();
    pwmWrite(Fan_PWM_Pin, PID_OP);
    Analog_Temp2_Val = 0.95 * Analog_Temp2_Val + 0.05 * (10000.0 / ((1023.0 / analogRead(Analog_Temp2_Pin)) - 1));
    Temp2_Steinhart_Val = Analog_Temp2_Val / Thermistor_Nom;
    Temp2_Steinhart_Val = log(Temp2_Steinhart_Val);
    Temp2_Steinhart_Val /= Thermistor_Beta;
    Temp2_Steinhart_Val += 1.0 / (Thermistor_Temp + 273.15);
    Temp2_Steinhart_Val = 1.0 / Temp2_Steinhart_Val;
    Temp2_Steinhart_Val -= 273.15;
    Temp2_Steinhart_Val = Temp2_Steinhart_Val * 1.8 + 32.0;
  }
  // Calculate Flow Rate
  if(Flow_Calculation.check() == 1){
    Flow_Rate = (Flow_Total / 10.0) * 0.68913; //Flow meter; 1380 pulses/Liter or 5224 pulses/gal, gph = pps * 3600/5224 or .68913
    Flow_Total = 0;
  }
 
  // Calculate Flow Interlock
  if(Flow_Interlock.check() == 1){
    Flow_Interlock_Val = Flow_Interlock_Total;
    Flow_Interlock_Total = 0;
  }
  // Calculate Fan RPM's
  if(Fan_RPM_Calculation.check() == 1){
    Fan_RPM_Rate = Fan_RPM_Total * 10 / 2;
    Fan_RPM_Total = 0;
  }
 
  // Read Encoder
  long Encoder_Val = Encoder_Knob.getPosition();
  if(Seq_Step_Num == 1){
    Encoder_Inc = Encoder_Knob.isIncrementing();
  }
  // Calculate Timer Setpoint
  Timer_Setpt = float(Encoder_Val) * 10.0;
  // Calculate Joules (not accurate because of analog reading can only occur so fast)
  if(Joules_Calculation.check() == 1 & Seq_Step_Num == 5){
    Joules_Total = Joules_Total + Analog_Amps_Val * Analog_Volt_Val * 0.100;
  }
  // Set Debounce timer to max value to avoid rollover of value
  if(Button_Debounce >= 10000){
    Button_Debounce = 10000;
  }
  // Begin Sequence Logic
  switch(Seq_Step_Num){
    // Step 1 Wait for pushbutton toggle ON
    case(1):
      //Encoder LED's are reverse logic, Zero is ON
      digitalWrite(Encoder_LED_BLU_Pin, 1);
      digitalWrite(Encoder_LED_GRN_Pin, 1);
      digitalWrite(Encoder_LED_RED_Pin, 1);
      if(Button_Toggle_State == HIGH){
        //Encoder LED's are reverse logic, Zero is ON
        digitalWrite(Encoder_LED_BLU_Pin, 1);
        digitalWrite(Encoder_LED_GRN_Pin, 0);
        digitalWrite(Encoder_LED_RED_Pin, 1);
        Seq_Step_Num++;
        break;
      }
      break;
    
    // Step 2 Wait for Case, also store setpoint if changed
    case(2):
      //Only store new setpoint if it has changed
      if(eeData != Timer_Setpt){
        eeData = Timer_Setpt / 10.0;
        EEPROM.put(eeAddress, eeData);
      }
      if(Case_Sensor_State == LOW){
        Timer_Actual = 0;
        Seq_Step_Num++;
        break;
      }
      if(Button_Toggle_State == LOW){
        Seq_Step_Num = 1;
        break;
      }
      break;
 
    // Step 3 Wait for Load Case Delay
    case(3):
      if(Case_Sensor_State == LOW & Timer_Actual >= Load_Delay_Setpt){
        Seq_Step_Num++;
        break;
      }
      if(Case_Sensor_State == HIGH){
        Seq_Step_Num = 2;
        break;
      }
      break;
    // Step 4 Initialize annealing timer and Energize Relay
    case(4):
      Timer_Actual = 0;
      Max_Amps_Val = Analog_Amps_Val;
      Relay_State = HIGH;
      Joules_Total = 0;
      Joules_Calculation.reset();
      //Encoder LED's are reverse logic, Zero is ON
      digitalWrite(Encoder_LED_BLU_Pin, 1);
      digitalWrite(Encoder_LED_GRN_Pin, 1);
      digitalWrite(Encoder_LED_RED_Pin, 0);
      Seq_Step_Num++;
      break;
    // Step 5 Wait for Timer at Setpoint
    case(5):
      Timer_Display = Timer_Actual;
      Max_Amps_Val = Analog_Amps_Val;
      if(Timer_Actual >= Timer_Setpt || Button_Toggle_State == LOW){
        Timer_Display = Timer_Actual;
        Seq_Step_Num++;
        break;
      }
      // if the amps go high then goto step 15
      if(Analog_Amps_Val > 14.0){
        Seq_Step_Num = 15;
        break;
      }
      // if low flow then goto step 14
      if(Flow_Interlock_Val <= 4){
        Seq_Step_Num = 14;
        break;
      }   
      break;
    // Step 6 Denergize Relay
    case(6):
      Relay_State = LOW;
      //Encoder LED's are reverse logic, Zero is ON
      digitalWrite(Encoder_LED_BLU_Pin, 0);
      digitalWrite(Encoder_LED_GRN_Pin, 1);
      digitalWrite(Encoder_LED_RED_Pin, 1);
      Seq_Step_Num++;
      break;
    // Step 7 Set Drop Servo to drop position & Reset Timer
    case(7):
      Timer_Actual = 0;
      //Drop_Servo.write(Drop_Pos_Setpt);
      Solenoid_State = HIGH;
      Seq_Step_Num++;
      break;
    // Step 8 Wait for drop position
    case(8):
      if(Timer_Actual >= Drop_Delay_Setpt){
        Seq_Step_Num++;
        break;
      }
      break;
    // Step 9 Check for case clear & Reset Timer
    case(9):
      Timer_Actual = 0;
      if(Case_Sensor_State == HIGH){
        Seq_Step_Num++;
        break;
      }
      break;
    // Step 10 Wait for case to clear
    case(10):
      if(Timer_Actual >= Case_Delay_Setpt){
        Seq_Step_Num++;
        break;
      }
      break;
    
    // Step 11 Set Servo to Home Position & Reset Timer
    case(11):
      Timer_Actual = 0;
      //Drop_Servo.write(Home_Pos_Setpt);
      Solenoid_State = LOW;
      Seq_Step_Num++;
      break;
    // Step 12 Wait for servo home
    case(12):
      if(Timer_Actual >= Home_Delay_Setpt){
        Seq_Step_Num++;
        break;
      }
      break;
    // Step 13 Return to Step 1
    case(13):
      if(Button_Toggle_State == HIGH){
        digitalWrite(Encoder_LED_BLU_Pin, 1);
        digitalWrite(Encoder_LED_GRN_Pin, 0);
        digitalWrite(Encoder_LED_RED_Pin, 1);
        Seq_Step_Num = 2;
        break;
      }
      else{
        Seq_Step_Num = 1;
        break;
      }
      break;
    // Step 14 Check for flow
    case(14):
      Relay_State = LOW;
      //Encoder LED's are reverse logic, Zero is ON
      digitalWrite(Encoder_LED_BLU_Pin, 0);
      digitalWrite(Encoder_LED_GRN_Pin, 1);
      digitalWrite(Encoder_LED_RED_Pin, 1);
      if(Button_Toggle_State == LOW){
        Seq_Step_Num = 7;
        break;
      }
      break;
    // Step 15 HIGH AMPS
    case(15):
      Relay_State = LOW;
      //Encoder LED's are reverse logic, Zero is ON
      digitalWrite(Encoder_LED_BLU_Pin, 0);
      digitalWrite(Encoder_LED_GRN_Pin, 1);
      digitalWrite(Encoder_LED_RED_Pin, 1);
      if(Button_Toggle_State == LOW){
        Seq_Step_Num = 7;
        break;
      }
      break;
  }
 
  // Write to LCD Display except for last 150mSec of timer to prevent overshoot from writing to I2C, didn't want the complexity of a timer interrupt
  if(LCD_Print_Data.check() == 1 && (Timer_Actual <= Timer_Setpt - 150 || Seq_Step_Num != 5)){
    //1st LCD Row
    lcd.home();
    lcd.print("Set;");
    lcd.setCursor(4,0);
    dtostrf(float(Timer_Setpt)/1000, 5, 2, Print_Buffer);
    lcd.print(Print_Buffer);
    lcd.print(" Act ");
    dtostrf(float(Timer_Display)/1000, 6, 3, Print_Buffer);
    lcd.print(Print_Buffer);
    //2nd LCD Row
    lcd.setCursor(0,1);
    dtostrf(Analog_Volt_Val, 4, 1, Print_Buffer);
    lcd.print(Print_Buffer);
    lcd.print("V");
    lcd.setCursor(6,1);
    if(Encoder_Inc){
      dtostrf(Fan_RPM_Rate, 4, 0, Print_Buffer);
      lcd.print(Print_Buffer);
      lcd.print("RPM");
    }
    else{
      dtostrf(Joules_Total, 5, 0, Print_Buffer);
      lcd.print(Print_Buffer);
      lcd.print("J "); 
    }
    lcd.setCursor(13,1);
    if(Seq_Step_Num == 5){
      dtostrf(Analog_Amps_Val, 5, 1, Print_Buffer);
      lcd.print(Print_Buffer);
      lcd.print(" A");
    }
    else{
      dtostrf(Max_Amps_Val, 5, 1, Print_Buffer);
      lcd.print(Print_Buffer);
      lcd.printByte(0); //Print Up Arrow
      lcd.print("A");
    }
    //3rd LCD Row
    lcd.setCursor(0,2);
    if(Encoder_Inc){
      lcd.print("T1;");
      dtostrf(Temp1_Steinhart_Val, 5, 1, Print_Buffer);
      lcd.print(Print_Buffer);
      lcd.print("F  ");
      lcd.setCursor(11,2);
      lcd.print("T2;");
      dtostrf(Temp2_Steinhart_Val, 5, 1, Print_Buffer);
      lcd.print(Print_Buffer);
      lcd.print("F");
    }
    else{
      lcd.print("F; ");
      dtostrf(Flow_Rate, 4, 1, Print_Buffer);
      lcd.print(Print_Buffer);
      lcd.print(" gph  ");
      lcd.setCursor(13,2);
      if(Case_Sensor_State == LOW){
        lcd.print("Case; Y");
      }
      else{
        lcd.print("Case; N");
      }
    }
    //4th LCD Row
    lcd.setCursor(0,3);
    lcd.print(Seq_Step_Desc[Seq_Step_Num]);
  }
}
/***************************
 * Flow Meter Interrupt    *
 ***************************/
 void Flow_Rising(void){
  Flow_Total++;
  Flow_Interlock_Total++;
 }
/***************************
 * Fan RPM Interrupt       *
 ***************************/
 void Fan_RPM_Rising(void){
  Fan_RPM_Total++;
 }
/*********************************
 * Positive Transition Sense     *
 *********************************/
void Push_Button_Toggle(void){
  if(Button_Debounce >= 1000){
    if(Button_Toggle_State == HIGH && Button_Debounce >= 100){
      Button_Toggle_State = LOW;
    }
    else{
      Button_Toggle_State = HIGH;
    }
    Button_Debounce = 0;
  }
}