// Version 6.3
// 05/31/2021
// Auto, Time, Joul and Pause modes.
// Streamlined Measure_A and Measure_V functions.
// Streamlined Measure_T by creating Convert_T.
// Added Measure_T back into Anneal_Sub.
// Added Convert_T to Error_Loop.
// Removed elapsed anneal time update durring Anneal_Sub.
// Function run time value comments updated.
// Start-up now Program 0 AUTO.
#include <Wire.h>
#include <EEPROM.h>
#include <elapsedMillis.h>
#include <JC_Button.h>
#include <OneWire.h>
#include "ABlocks_LiquidCrystal_I2C.h"
boolean b_Error = false;
char Print_Buffer[8] = "";
double Amperage = 0;
double Amp_Max = 0;
double Voltage = 0;
double Joules = 0;
const double Max_Amperes = 27.5;
const double V_Constant = 8.2750; // For resistors 51K and 6.8K,  57.8k/6.8k = 8.5 adjust as needed for accuracy
int Anneal_Time = 2500;
int Case_Program_Num = 0;
int Actual_Time = 0;
int Joule_Time = 0;
int Mode = 0;
int Anneal_Energy = 1200;
int Glow = 1023;
int Glow_Set = 50; // 1023 is dark, max glow is 0
int16_t C_Temp;
int16_t Last_Temp = 20;
const int A_Constant = 530; // Adjust as needed for accuracy
const int ls_Case_Name_size = 10;
const int Test_Anneal_Time = 2500;
const int Case_Settle = 500;
const int Drop_Delay = 10;
const int Door_Close = 10;
const int Detector_Threshold = 500; // Sets the detector trigger threshold of the IR receiver. 1000 = high
const int Fan_On_Temp = 120;
const int Test_Anneal_Energy = 1200;
const int Case_Glow_Program_Num = 20; // ls_Case_Name_size + 10
String s_Case_Text;
String s_Error_Text;
String s_Mode_Text;
//************************THIS IS WHERE YOU CUSTOMIZE CARTRIDGE CASE NAMES*****
//************************                                                   **
String ls_Case_Name[10] = { // 11 characters maximum                         **
  String("*AUTO MODE*"),    //                                               **
  String("NATO 5.56  "),    //                                               **
  String("6.5 PRC    "),    //                                               **
  String("300 WBY    "),    //                                               **
  String("Four       "),    //                                               **
  String("Five       "),    //                                               **
  String("Six        "),    //                                               **
  String("Seven      "),    //                                               **
  String("Eight      "),    //                                               **
  String("Test       ")};   //                                               **
//*****************************************************************************
//*****************************************************************************
LiquidCrystal_I2C lcd(0x27,20,4);
Button
  Tm_Up(2),   // Add time
  Tm_Dn(3),   // Subtract time
  Pgm_Up(4),  // Next program
  Pgm_Dn(5),  // Previous program
  Int_Err(6); // Interupt anneal and clear error
elapsedMillis Annealing; // Used to measure total annealing time
elapsedMillis Measuring; // Used to calculate joules
void fnc_EEPROM_updateDouble(int ee, double value) {
  byte* p = (byte*)(void*)&value;
  for (int i = 0; i < sizeof(value); i++)
  EEPROM.update(ee++, *p++);
}
double fnc_EEPROM_readDouble(int ee) {
  double value = 0.0;
  byte* p = (byte*)(void*)&value;
  for (int i = 0; i < sizeof(value); i++)
  *p++ = EEPROM.read(ee++);
  return value;
}
void EEPROM_Initialize() {
  fnc_EEPROM_updateDouble((int)(0*4),3000);
  fnc_EEPROM_updateDouble((int)(1*4),3000);
  fnc_EEPROM_updateDouble((int)(2*4),3000);
  fnc_EEPROM_updateDouble((int)(3*4),3000);
  fnc_EEPROM_updateDouble((int)(4*4),3000);
  fnc_EEPROM_updateDouble((int)(5*4),3000);
  fnc_EEPROM_updateDouble((int)(6*4),3000);
  fnc_EEPROM_updateDouble((int)(7*4),3000);
  fnc_EEPROM_updateDouble((int)(8*4),3000);
  fnc_EEPROM_updateDouble((int)(255*4),(-537537));
}
double Measure_A() { // 1m sec to run function, 30 amp sensor returns 66mA/V; 20 amp sensor, 100mA/V, both OA = 2.5V input
  Amperage = (analogRead(A1) - A_Constant) * 5 / 67.584; // Sensor returns 66mV/A (0.66x1024=67.584) Starts at 2.5V(511), adjust A_Constatnt for accuracy
  if (Amperage >= 0) {                      
    if (Amperage > Amp_Max)
      Amp_Max = Amperage;
  }
  else
    Amperage = 0;
}
double Measure_V() { // 1m sec to run function, Voltage sensor 6.8K and 51K reistors
  Voltage = analogRead(A0) * V_Constant * 5 / 1024;
  if (Voltage < 0)
    Voltage = 0;
}
int16_t Measure_T(int x, byte start) { // 6m sec to run function, DS18B20 Temperature Sensor 4.7K pullup rsistor on middle pin to +5
  OneWire ds(7);
  byte i;
  byte data[2];
  do {
    ds.reset();
    ds.write(0xCC); // Skip command
    ds.write(0xBE); // Read 1st 2 bytes of Scratchpad
    for (i=0; i<2; i++) data[i] = ds.read();
    C_Temp = (data[1]<<8) | data[0];
    C_Temp >>= 4;
    if (data[1] & 128) C_Temp |= 61440;
    if (data[0] & 8) ++C_Temp;
    ds.reset();
    ds.write(0xCC); //Skip command
    ds.write(0x44,1); //Start conversion, assuming 5V connected
    if (start) delay(1000);
  }
  while
    (start--);
  if (C_Temp >= 82) { // ZVS PCB opperating temperature -58F to 230F (-50C to 110C)
    digitalWrite(8,LOW);
    digitalWrite(10,HIGH);
    lcd.setCursor(17,1);
    lcd.print("MAX");
    s_Error_Text = String ("  TEMP HIGH ERROR!  ");
    b_Error = true;
  }
}
void Convert_T() { // 7m sec to run function
  Measure_T(7,0);
  double F_Temp;
  if (C_Temp <= Last_Temp - 20) //check for bogus data
    return;
  Last_Temp = C_Temp;
  F_Temp = C_Temp * 9 / 5 + 32; // Convert C to F adjust as needed for accuracy
  if
    (F_Temp >= Fan_On_Temp) digitalWrite(10,HIGH); // Turn fan on if temp gets high
  else
    digitalWrite(10,LOW);
  lcd.setCursor(17,1);
  dtostrf(F_Temp,3,0,Print_Buffer);
  lcd.print(Print_Buffer);
}
void Anneal_Sub() { // 1m sec to run function witout Measure_T, 6m sec with Measure_T
  Int_Err.read();
  Measure_T(7,0);
  Measure_A();
  Measure_V();
  Glow = analogRead(A3);
  Joule_Time = Measuring;
  Measuring = Measuring - Joule_Time;
  Joules = Joules + (Voltage * Amperage * Joule_Time / 1000);
  //lcd.setCursor(3,2);                              // These 3 update timer durring anneal
  //dtostrf(float(Annealing)/1000,4,2,Print_Buffer); // taking 8m sec to execute and were
  //lcd.print(Print_Buffer);                         // removed to speed-up Anneal_Sub
// Anneal manual button interrupt
  if (Int_Err.wasPressed()) {
    s_Error_Text = String (" ANNEAL INTERUPTED! ");
    b_Error = true;
    return;
  }
// Anneal over max amperes interrupt
  if (Amperage >= Max_Amperes) {
    s_Error_Text = String ("   AMP HIGH ERROR   ");
    b_Error = true;
  }
}
void Anneal_Main() {
  //int Func_Time; // Used for computing time of a function
  b_Error = false;
  Joules = 0;
  Amp_Max = 0;
  Glow = 1023;
  Convert_T();
  delay(Case_Settle);
  digitalWrite(8,HIGH); // SSR relay on
  Annealing = 0;
  Measuring = 0;
  lcd.setCursor(0,3);
  lcd.print("   ANNEALING CASE   ");
  if (Case_Program_Num == 0) {
    while (Glow > Glow_Set && !b_Error) {
      //elapsedMillis Timer;      // Used for computing time of a function
      Anneal_Sub();
      //Func_Time = Timer;        // Used for computing time of a function
      //s_Error_Text = Func_Time; // Used for computing time of a function
      //b_Error = true;           // Used for computing time of a function
    }
  }
  else if (Mode == 1) {
    while ((Annealing <= Anneal_Time - 10) && !b_Error) // Skip last 10m sec to avoid overshoot
      Anneal_Sub();
    while (Annealing < Anneal_Time && !b_Error)
      delay(1);
  }
  else
    while ((Joules < Anneal_Energy - 2) && !b_Error) // Skip last 2J to avoid overshoot
      Anneal_Sub();
// Anneal complete
  digitalWrite(8,LOW);
  Joule_Time = Measuring;
  Actual_Time = Annealing;
  Joules = Joules + (Voltage * Amperage * Joule_Time / 1000);
  lcd.setCursor(15,2);
  dtostrf(Joules,4,0,Print_Buffer);
  lcd.print(Print_Buffer);
  lcd.setCursor(3,2);
  dtostrf(float(Actual_Time)/1000,4,2,Print_Buffer);
  lcd.print(Print_Buffer);
  lcd.setCursor(9,1);
  dtostrf(Amp_Max,3,1,Print_Buffer);
  lcd.print(Print_Buffer);
  lcd.setCursor(10,2);
  dtostrf(Glow,4,0,Print_Buffer);
  lcd.print(Print_Buffer);
  Convert_T();
  if (b_Error)
      Error_Loop();
// Case detector
  do {
    digitalWrite(9,HIGH); // Trap door relay open
    lcd.setCursor(0,3);
    lcd.print(" *****CASE JAM***** ");
  }
  while
    (analogRead(A2) < Detector_Threshold);
  delay(Drop_Delay);
  digitalWrite(9,LOW); // Trap door relay close
  delay(Door_Close);
  lcd.setCursor(0,3);
  lcd.print("  WAITING FOR CASE  ");
}
void Error_Loop() {
  delay(26);
  do {
    lcd.setCursor(0,3);
    lcd.print(s_Error_Text);
    Convert_T();
    Int_Err.read();
  } while
    (!Int_Err.wasPressed());
  b_Error = false;
  delay(26);
}
void Anneal_Set() { // Set Anneal Time
  if (Case_Program_Num == 0) {
    Glow_Set = fnc_EEPROM_readDouble(Case_Glow_Program_Num*4);
    return;
  }
  if (Mode == 1) {
    if (Case_Program_Num != (ls_Case_Name_size - 1))
      Anneal_Time = fnc_EEPROM_readDouble(Case_Program_Num*4);
    else
      Anneal_Time = Test_Anneal_Time;
  }
  else // Set Anneal Energy
    if (Case_Program_Num != (ls_Case_Name_size - 1))
      Anneal_Energy = fnc_EEPROM_readDouble((Case_Program_Num + 10)*4);
    else
      Anneal_Energy = Test_Anneal_Energy;
}
void Anneal_Change() {
  if (Case_Program_Num == 0) {
    if (fnc_EEPROM_readDouble(Case_Glow_Program_Num*4) != Glow_Set)
      fnc_EEPROM_updateDouble((int)(Case_Glow_Program_Num*4),Glow_Set);
    return;
  }
  if (Mode == 1) {// Anneal Time Change
    if ((fnc_EEPROM_readDouble(Case_Program_Num*4) != Anneal_Time) && (Case_Program_Num != (ls_Case_Name_size - 1)))
      fnc_EEPROM_updateDouble((int)(Case_Program_Num*4),Anneal_Time);
  }
  else // Anneal_Energy_Change()
    if ((fnc_EEPROM_readDouble((Case_Program_Num + 10)*4) != Anneal_Energy) && (Case_Program_Num != (ls_Case_Name_size - 1)))
      fnc_EEPROM_updateDouble((int)((Case_Program_Num + 10)*4),Anneal_Energy);
}
void Display() { // 52m sec to run funtion
  s_Case_Text = ls_Case_Name[(Case_Program_Num)];
  lcd.setCursor(0,0);
  lcd.print(Case_Program_Num);
  lcd.setCursor(2,0);
  lcd.print(s_Case_Text);
  if (Case_Program_Num == 0)  {
    lcd.setCursor(14,0);
    lcd.print("G=");
    dtostrf(Glow_Set,4,0,Print_Buffer);
  }
  else if (Mode == 1) {
    lcd.setCursor(14,0);
    lcd.print("T=");
    dtostrf(float(Anneal_Time)/1000,4,2,Print_Buffer);
  }
  else {
    lcd.setCursor(14,0);
    lcd.print("J=");
    dtostrf(Anneal_Energy,4,0,Print_Buffer);
  }
  lcd.setCursor(16,0);
  lcd.print(Print_Buffer);
  Measure_V();
  lcd.setCursor(2,1);
  dtostrf(Voltage,3,1,Print_Buffer);
  lcd.print(Print_Buffer);
  Convert_T();
  if (b_Error)
    Error_Loop();
}
void setup() {
  if ((fnc_EEPROM_readDouble((int)(255*4)) != -537537))
    EEPROM_Initialize();
  Tm_Up.begin();     // Pin 2  Blue,   Add time
  Tm_Dn.begin();     // Pin 3  Purple, Subtract time
  Pgm_Up.begin();    // Pin 4  Yellow, Next program
  Pgm_Dn.begin();    // Pin 5  Green,  Previous program
  Int_Err.begin();   // Pin 6  Brown,  Interupt anneal and clear error
  Measure_T(7,1);     // Pin 7  Temperature sensor
  pinMode(8,OUTPUT); // Pin 8  SSR control
  pinMode(9,OUTPUT); // Pin 9  Trap door control
  pinMode(10,OUTPUT);// Pin 10 Fan
  pinMode(A0,INPUT); // Pin A0 Measure voltage
  pinMode(A1,INPUT); // Pin A1 Measure Amperage
  pinMode(A2,INPUT); // Pin A2 IR break beam
  pinMode(A3,INPUT); // Pin A3 Flame sensor
  lcd.begin();
  lcd.noCursor();
  lcd.clear();
  lcd.display();
  lcd.setCursor(1,0);
  lcd.print(":");
  lcd.setCursor(14,0);
  lcd.print("T=");
  lcd.setCursor(0,1);
  lcd.print("V=");
  lcd.setCursor(7,1);
  lcd.print("A=");
  lcd.setCursor(14,1);
  lcd.print("F=");
  lcd.setCursor(0,2);
  lcd.print("Ta=");
  lcd.setCursor(19,2);
  lcd.print("J");
  lcd.setCursor(8,2);
  lcd.print("G=");
  lcd.setCursor(0,3);
  lcd.print("  WAITING FOR CASE  ");
  digitalWrite(8,LOW); // Annealer power SSR
  digitalWrite(9,LOW); // Trap Door
  digitalWrite(10,LOW); // Fan
}
void loop() {
  Display();
  Tm_Up.read();   // Pin 2 Add time
  Tm_Dn.read();   // Pin 3 Subtract time
  Pgm_Up.read();  // Pin 4 Next program
  Pgm_Dn.read();  // Pin 5 Previous program
  Int_Err.read(); // Pin 6 Cycle Mode, interupt anneal and clear error
// Interup button pressed cycle mode, Mode 0 pause, Mode 1 time anneal, Mode 2 joule anneal
  if (Int_Err.wasPressed()) {
    if (Case_Program_Num == 0) {
      if (Mode != 0)
        Anneal_Change();
      if (++Mode > 1)
        Mode = 0;
    }
    else {
      if (Mode != 0) // Changing out of modes 1-3 check for value change
        Anneal_Change();
      if (++Mode > 2) // Cycle from Mode 2 back to Mode 0
        Mode = 0;
      if (Mode == 2) // Going from Mode 1 to Mode 2, set Joule value from EEPROM
        Anneal_Set();
    }
    Display();
  }
  if (Mode == 0) {
    s_Error_Text = String ("     PAUSE MODE     ");
    Error_Loop();
    Mode = 1;
    Anneal_Set(); // Going from Mode 0 to Mode 1, set Time value from EEPROM
    lcd.setCursor(0,3);
    lcd.print("  WAITING FOR CASE  ");
  }
// Up time/energy button pressed Pin 2
  if (Tm_Up.wasPressed()) {
    if (Mode == 1 && Case_Program_Num != 0) { //Max anneal time 9.99 seconds
      Anneal_Time = Anneal_Time + 50;
      if (Anneal_Time > 9950)
        Anneal_Time = 9990;
    }
    else if (Mode == 2 && Case_Program_Num != 0) { //Max anneal energy 9999 jouls
      Anneal_Energy = Anneal_Energy + 5;
      if (Anneal_Energy > 9999)
        Anneal_Energy = 9999;
    }
    else { // Max Glow_Set 1023
      if (++Glow_Set > 1023)
        Glow_Set = 1023;
    }
    Display();
  }
// Up time/energy button held Pin 2
  while (Tm_Up.pressedFor(750)) { //Max anneal time 9.99 seconds
    if (Mode == 1 && Case_Program_Num != 0) { //Max anneal time 9.99 seconds
      Anneal_Time = Anneal_Time + 50;
      if (Anneal_Time > 9950)
        Anneal_Time = 9990;
    }
    else if (Mode == 2 && Case_Program_Num != 0) { //Max anneal energy 9999 jouls
      Anneal_Energy = Anneal_Energy + 5;
      if (Anneal_Energy > 9999)
        Anneal_Energy = 9999;
    }
    else { // Max Glow_Set 1023
      if (++Glow_Set > 1023)
        Glow_Set = 1023;
    }
    Display();
    delay(50);
    Tm_Up.read();
  }
// Down time/energy button pressed Pin 3
  if (Tm_Dn.wasPressed()) { //Min anneal time 0.25 seconds
    if (Mode == 1 && Case_Program_Num != 0) {
      Anneal_Time = Anneal_Time - 50;
      if (Anneal_Time <= 250)
        Anneal_Time = 250;
    }
    else if (Mode == 2 && Case_Program_Num != 0) { //Min anneal energy 500 jouls
      Anneal_Energy = Anneal_Energy - 5;
      if (Anneal_Energy <= 100)
        Anneal_Energy = 100;
    }
    else { //Min Glow_Set 0
      if (--Glow_Set < 0)
        Glow_Set = 0;
    }
    Display();
  }
// Down time/energy button held Pin 3
  while (Tm_Dn.pressedFor(750)) {
    if (Mode == 1 && Case_Program_Num != 0) {
      Anneal_Time = Anneal_Time - 50;
      if (Anneal_Time <= 250)
        Anneal_Time = 250;
    }
    else if (Mode == 2 && Case_Program_Num != 0) { //Min anneal energy 500 jouls
      Anneal_Energy = Anneal_Energy - 5;
      if (Anneal_Energy <= 100)
        Anneal_Energy = 100;
    }
    else { // Min Glow_Set 0
      if (--Glow_Set < 0)
        Glow_Set = 0;
    }
    Display();
    delay(50);
    Tm_Dn.read();
  }
// Up program button pressed Pin 4
  if (Pgm_Up.wasPressed()) {
    Anneal_Change();
    if ((++Case_Program_Num > (ls_Case_Name_size - 1)))
      Case_Program_Num = 0;
    Anneal_Set();
    Display();
  }
// Down program button pressed Pin 5
  if (Pgm_Dn.wasPressed()) {
    Anneal_Change();
    if (--Case_Program_Num < 0)
      Case_Program_Num = (ls_Case_Name_size - 1);
    Anneal_Set();
    Display();
  }
// Case detector IR Emitter diode + 150 Ohm reistor to 5V, IR Receiver Diode + 10K to gnd
  if (analogRead(A2) < Detector_Threshold)
    Anneal_Main();
}