/* Hello Wokwi! */

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);
/*************************************************************************************

  Ioquir Sotile
   Auxiliar no processo de fabricação de cerveja caseira
   Curitiba - Paraná - Brasil
   Início da programação start of project: 26/out/2015.

  This program is designed to help home brewing proccess

  Prepared to work with a 4x20 display.
  For English messages, uncomment #define ENGLISH
  -- not yet prepared for fahrenheit . Sorry! See DisplayTemperature function.

  Notas da subversão / Subversion Notes:
  - Corrected time display when time is greater than 59 minutes
  - Intermitent active buzzer to alert when temperature is over desired
  - ESC button functions: mute alarm (intermitent buzzer), go to step
  - Use interrupt for Ok button
  - extend boil time when user takes too long to add a hop, change total boil time displayed.
  - process whirflock as a hop 
  
  buggy / not working or implemented or implementing:
  - customize mash and boil profiles // won't be done in this version because of UNO memory limitations
  - ESC button function go to substep
  - adjust functions behavor after ESC button function go to step
  - add whirflock alert is not working
  - measure water temperature before warming up to calculate how may degrees it may be at startup


  Units are:
    seconds (millis()/1000 or
    minutes (profiles, whirflock time, recirculate time definitions)

    Celsius


  some sources:
  http://www.pjrc.com/teensy/td_libs_OneWire.html
  http://henrysbench.capnfatz.com/henrys-bench/arduino-sensors-and-input/keyes-ky-040-arduino-rotary-encoder-user-manual/
  www.zonamaker.com.br/simbolos-personalizados-no-display-lcd-com-i2c/

**************************************************************************************/

#define On    1
#define Off   0

//#define TEST    1     //uncomment for code test
//#define ENGLISH 1  //uncomment for english messages. If not, it will be BR portuguese.
#define COMMON_ANODE //  comment if using RGB common catode LEDs
//#define POTENTIOMETER //uncomment if using a 5K potentiometer to input values and menus, instead will use a rotary encoder


// display inicial definitions
/* if you use a 20x4 with parallel interface, you shoud set:
  #include <LiquidCrystal.h>
  LiquidCrystal lcd(7, 6, 5, 4, 3, 2);           // select the pins used on the LCD panel
*/
// for a display with a serial interface adapter:
#include <Wire.h>
// set the LCD address to 0x27 for a 20 chars and 4 line display

// temperature sensor initial definitions
#include <OneWire.h>

OneWire ONE_WIRE_BUS(8); // select the digital pin used to connect sensor(s)

/*-----( Declare Variables )-----*/
// special characters
byte Cedilha[8] = {0x0, 0x0, 0xe, 0x11, 0x10, 0x15, 0xe, 0x8}; // character ç
byte ATil   [8] = {0xd, 0x16, 0xe, 0x1, 0xf, 0x11, 0xf}; //character ã
byte AAgudo [8] = {0x2, 0x4, 0xe, 0x1, 0xf, 0x11, 0xf}; //character á
/*byte Thermo [8] = {B00100,B01010,B01010,B01110,B01110,B11111,B11111,B01110}; //icon for termometer
  uint8_t bell[8]  = {0x4,0xe,0xe,0xe,0x1f,0x0,0x4};
  uint8_t note[8]  = {0x2,0x3,0x2,0xe,0x1e,0xc,0x0};
  uint8_t clock[8] = {0x0,0xe,0x15,0x17,0x11,0xe,0x0};
  uint8_t heart[8] = {0x0,0xa,0x1f,0x1f,0xe,0x4,0x0};
  uint8_t duck[8]  = {0x0,0xc,0x1d,0xf,0xf,0x6,0x0};
  uint8_t check[8] = {0x0,0x1,0x3,0x16,0x1c,0x8,0x0};
  uint8_t cross[8] = {0x0,0x1b,0xe,0x4,0xe,0x1b,0x0};
  uint8_t retarrow[8] = {  0x1,0x1,0x5,0x9,0x1f,0x8,0x4};
*/

// #constants
#define Choose     0
#define Prepare    1
#define Mash       2
#define Lauther    3
#define Boil       4
#define Cool       5
#define Whirlpool  6
#define End        7

#define BoilUp     0
#define BoilOn     1
#define BoilOk     2
#define BoilRest   3


const int MashOutTemperature  = 76;
#ifdef TEST
const int MashOutTime         =  1;
#else
const int MashOutTime         = 10;
#endif
const int ClarificationTime   = 10; // time recirculating the worth before Lauther
const int RestTime            =  5;
const int CoolToTemperature   = 25; // final temperature of cooling step
const int Recirculate         =  0;
const int WhirflockTime       = 14; // 14 not to overlap hop at 15' Should not be greater than 14 and lower than 11, or rearrange boil profiles.
const int WhirlpoolTime       =  5;
const int Sparge              =  1;
const int TemperatureTolerance=  1; // mash temperature tolerance acepted,  + or - 
const int DebouncingTime      =  1;
// PINS digital

#define Encoder0PinA       0
#define Encoder0PinB       1
//#define EscButton        2 // Interrupt pin in NANO. Used by ESC but no need to define.
#define OkButton           3 // Interrupt pin in NANO. Used by OK  without interruption, so that need to define. Used also as an interrupt.
// parallel lcd       7,6,5,4,3,2    // only if you use a parallel display, note the conflicting ports!!
// free                    4
// free                    5
// free                    6
// free                    7
//      ONE_WIRE_BUS       8 // definitiron above. Here only for documentation
// temperature sensor conector pins:
// 1 = +5V
// 2 = DATA
// 3 = GND
#define Buzzer             9
#define IntermitentBuzzer 10
#define RGBLedRedPin      11
#define RGBLedGreenPin    12
#define RGBLedBluePin     13

// PINS analogic
#define SensorTemp      0
#define Potentiometer   1

// vars

boolean WaitForOkButton, OkButtonWasPressed, EscButtonWasPressed, SoundAlarm, SoundAlarmCanceled,
        HeatIsOn, YellowAlert, RedAlert;
byte  MashOption,        BoilOption,
      CurrentStage,      PreviousStage,
      CurrentMashStep,   CurrentBoilStep,
      HopNumber,
      Option, LastOption,
      Red, Green, Blue,
      present = 0, type_s, data[12], addr[8],
      Encoder0PinALast,  AuxNumberLastMessage, NumberLastMessage;

int   Minutes, Seconds, BoilTotalDelaySeconds, BoilDelaySeconds, Aux, i;
volatile int EscButtonState;
volatile boolean StepHasSubsteps;
long          Now;
unsigned long HalfSecondAgo,  TimeStageStarted, TimeMashStepStarted, CurrentBoilStepStarted,
         TimeLastOkInterrupt, TimeLastEscInterrupt;

float CurrentTemperature;

String StrAddHop = "";
String ProgramString = "Sotile Brewing  v1.1";

struct StructSteps {
  String Name;
  int    InitialTemperature;
  int    NumberOfSteps;
  int    Temperature[4]; // 3 steps plus mashout
  int    Time[4];        // 3 steps plus mashout
};

struct StructBoil {
  int MinutesTotal;
  int NumberOfHops;
  int TimeOfHop[5];  // max of 4 hoppings during boil + whirflock
  int WhichIsWhirflock;
};

struct StructSteps StepProfile;
struct StructBoil  BoilProfile;


void CleanDisplay() {
  lcd.clear();
}

int Message(int Which, int B) {
  for (i = 0; i  < 20; i++) {
    lcd.setCursor(i, 3);
    lcd.print(" ");
  }

  lcd.setCursor(0, 3);
#ifdef ENGLISH
  switch (Which) {
    case  0 : lcd.print(F("                    ")); break; // F function loads strings into flash rather than in SRAM
    case  1 : lcd.print(F("  ** warm up!!!  ** ")); break;
    case  2 : lcd.print(F("  Recirculate wort  ")); break;
    case  3 : lcd.print(F("    Sparge  (OK)    ")); break;
    case  4 : lcd.print(F("    Do whirlpool    ")); break;
    case  5 : lcd.print(F("   Heat the wather  ")); break;
    case  6 : lcd.print(F("  Add grains  (OK)  ")); break;
    case  7 : lcd.print(F(" Mash complete (OK) ")); break;
    case  8 : lcd.print(F("Warm up til ramp(OK)")); break;
    case  9 : lcd.print(F("     Recirculate    ")); break;
    case 10 : lcd.print(F("Heat to boiling!(OK)")); break;
    case 11 : lcd.print(F(" Boil completed (OK)")); break;
    case 12 : lcd.print(F(" Add hop "           ));
      lcd.print(   StrAddHop             ); break;
    case 13 : lcd.print(F(" Add whirflock (OK) ")); break;
    case 14 : {
        lcd.print(F("  Rest for "));
        lcd.print(RestTime);
        lcd.print(F(" min.  "));
      }          break;
    case 15 : lcd.print(F("    Cool the wort   ")); break;
    case 16 : lcd.print(F(" Remove chiller (OK)")); break;
    case 17 : lcd.print(F(" Press (OK) to start")); break;
    case 18 : lcd.print(F("Exit with no change!")); break;
    case 19 : lcd.print(F("Delayed      minutes")); break;
  }
#else
  switch (Which) {
    case  0 : lcd.print(F("                    ")); break;
    case  1 : lcd.print(F("  ** Aquecer!!!  ** ")); break;
    case  2 : lcd.print(F("  Recircular mosto  ")); break;
    case  3 : {
        lcd.print(F(" Lavar os graos (OK)"));
        lcd.setCursor(12, 3); lcd.write(char(1)); break; // writes an "ã"
      }
    case  4 : lcd.print(F("  Fazer whirlpool   ")); break;
    case  5 : lcd.print(F("  Aquecer a agua    "));
      lcd.setCursor(12, 3); lcd.write(char(2)); break; // writes an "á"
    case  6 : {
        lcd.print(F("Adicionar graos (OK)"));
        lcd.setCursor(12, 3); lcd.write(char(1)); break; // writes an "ã"
      }
    case  7 : lcd.print(F(" Mash completo (OK) ")); break;
    case  8 : lcd.print(F("Aquecer p/ rampa(OK)")); break;
    case  9 : lcd.print(F("     Recircular     ")); break;
    case 10 : lcd.print(F("Aquecer p/ferver(OK)")); break;
    case 11 : lcd.print(F(" Fim da fervura (OK)")); break;
    case 12 : lcd.print(F("Ad.lupulo "          ));
      lcd.print(   StrAddHop             ); break;
    case 13 : lcd.print(F("Junte whirflock (OK)")); break;
    case 14 : lcd.print(F(" Descansar 10 min.  ")); break;
    case 15 : lcd.print(F("  Resfriar o mosto  ")); break;
    case 16 : lcd.print(F("Retirar chiller (OK)")); break;
    case 17 : lcd.print(F("   Pressione (OK)   ")); break;
    case 18 : lcd.print(F("Volta sem alteracao!"));
      lcd.setCursor(16, 3); lcd.print(char(0));   // writes a  "ç"
      lcd.print(char(1));    // writes an "ã"
      break;
    case 19 : lcd.print(F("Atrasou      minutos")); break;
  }
#endif

  if (Which < 18)
    AuxNumberLastMessage = Which;
  if (B) {
    tone(Buzzer, 2000);
    delay(200);
    noTone(Buzzer);
  }
}


void ShowAlert () {
  /* RGB common catode for display
       R    G    B
     255,   0,   0 = red
       0, 255,   0 = green
       0,   0, 255 = blue
     255, 255,   0 = yellow
      80,   0,  80 = purple
       0, 255, 255 = aqua
  */
  if (RedAlert) {
    Red   = 255;
    Green =   0;
    Blue  =   0;
    if (!SoundAlarm) {
      SoundAlarm = true;
      SoundAlarmCanceled = false;
      digitalWrite(IntermitentBuzzer, HIGH);
    }
  } else {
    if (YellowAlert) {  // blue seems better than yellow!
      Red   =   0;
      Green =   0;
      Blue  = 255;
    } else if ((CurrentStage == Choose) || (WaitForOkButton && !OkButtonWasPressed)) { // waiting for an input = aqua color
      Red   =   0;
      Green = 255;
      Blue  = 255;
    } else { // green color
      Red   =   0;
      Green = 255;
      Blue  =   0;
    }
    if (SoundAlarm) {
      SoundAlarm = false;
      SoundAlarmCanceled = false;
      digitalWrite(IntermitentBuzzer, LOW);
    }
  }
#ifdef COMMON_ANODE
  //  for common anode, we need to make digital pin near to 0, so the color is:
  //  color = 255 - color;
  Red   = (255 - Red  );
  Green = (255 - Green);
  Blue  = (255 - Blue );
#endif
  analogWrite(RGBLedRedPin  , Red  );
  analogWrite(RGBLedGreenPin, Green);
  analogWrite(RGBLedBluePin , Blue );
}

void TurnMotor(int OnOff) {
  // to be developed
}

void TurnHeat(int OnOff) {
  // to be adapted
  if (OnOff == On) {
    Message( 1, true); //" ** warm up!!!  **"
    HeatIsOn    = true;
    YellowAlert = true;
  } else {
    Message ( 0, false); // cleans message
    HeatIsOn  = false;
    YellowAlert = false;
  }
}

void TurnPump (int Witch, int OnOff) {
  if (OnOff = On) {
    switch (Witch) {
      case Recirculate : Message( 2, false); break; //"Recirculate wort"
      case Sparge      : Message( 3, false); break; //"Sparge  (OK)"
      case Whirlpool   : Message( 4, false); break; //"Do whirlpool"
    }
    YellowAlert = true;
  } else {
    Message( 0, false);  // cleans message
    YellowAlert = false;
  }
}

void MessageStageName (int WhichName) {
#ifdef ENGLISH
  switch (WhichName) {
    case Choose:   {
        lcd.print(F("Choose"      ));  // translate here
        break;
      }
    case Prepare:  {
        lcd.print(F("Prepare"     ));
        break;
      }
    case Mash:     {
        lcd.print(F("Mash"        ));
        break;
      }
    case Lauther:  {
        lcd.print(F("Sparge"      ));
        break;
      }
    case Boil:     {
        lcd.print(F("Boil"        ));
        break;
      }
    case Cool:     {
        lcd.print(F("Cooling"     ));
        break;
      }
    case Whirlpool: {
        lcd.print(F("Whirlpool"   ));
        break;
      }
    default :      {
        lcd.print(F("Process end "));
        break;
      }
  }
#else
  switch (WhichName) {
    case Choose:   {
        lcd.print(F("Escolha"     ));
        break;
      }
    case Prepare:  {
        lcd.print(F("Prepara"));
        lcd.print(char(0));           // writea a  "ç"
        lcd.print(char(1));           // writes an "ã"
        lcd.print(F("o"   ));         break;
      }
    case Mash:     {
        lcd.print(F("Mostura"     ));
        break;
      }
    case Lauther:  {
        lcd.print(F("Filtragem"   ));
        break;
      }
    case Boil:     {
        lcd.print(F("Fervura"     ));
        break;
      }
    case Cool:     {
        lcd.print(F("Resfriamento"));
        break;
      }
    case Whirlpool: {
        lcd.print(F("Whirlpool"   ));
        break;
      }
    default :      {
        lcd.print(F("Fim"         ));
        break;
      }
  }
#endif
}
void MessageStage(int Which) {
  for (i = 0; i  < 20; i++) { // 20 = lcd rows
    lcd.setCursor(i, 0);
    lcd.print(" ");
  }
  lcd.setCursor(0, 0);
#ifdef ENGLISH
  lcd.print(F("Stage: "));
#else
  lcd.print(F("Etapa: "));
#endif
  switch (Which) {
    case Choose :
    case Prepare:  MessageStageName (Which); break;   // translate here
    case Mash:     MessageStageName (Which);
      lcd.setCursor(15, 0);  lcd.print("1/");
      lcd.print(StepProfile.NumberOfSteps);
      break;
    case Lauther:  MessageStageName (Which); break;
    case Boil:     MessageStageName (Which);
      lcd.print(F("  "));
      lcd.print(BoilProfile.MinutesTotal + (BoilTotalDelaySeconds / 60));
      lcd.print("\'");
      break;
    case Cool:
    case Whirlpool: MessageStageName (Which);  break;
  }
}

void DisplayATime(int X, int Min, int Sec) {
  if (Min >= 60) { // if time is more than one hour, shows using format hh:mm (or 12h00 if ENGLISH is not deffined)
    Aux = Min   / 60; // calculate hours
    lcd.setCursor(X , 1);
    if (Aux < 10)
      lcd.print(" ");
    lcd.print(Aux);  // prints hours
    Aux = Min   % 60; // calculate minutes that are not full hours
#ifdef ENGLISH
    lcd.print(":");
#else
    lcd.print("h");
#endif
    if ((int)Aux < 10)
      lcd.print("0");
    lcd.print(Aux);
  } else {         // else shows using format  mm'ss
    if (Min >= 10)  lcd.setCursor(X  , 1);
    else         lcd.setCursor(X + 1, 1);
    lcd.print(Min); lcd.print("\'");
    if ((int)Sec < 10)
      lcd.print("0");
    lcd.print(Sec);
  }
}

void DisplayTimes() {
  // displays total time:
  int   aux;
  Now     = millis() / 1000;
  Minutes =  (int) (Now / 60);
  Seconds =  (int) (Now - (Minutes * 60));
  DisplayATime(0, Minutes, Seconds);

  // displays stage time:
  Minutes =  (int) ((Now - TimeStageStarted)        / 60);
  Seconds =  (int) ((Now - TimeStageStarted) - (Minutes * 60));
  DisplayATime(6, Minutes, Seconds);

  switch (CurrentStage) {
    case Mash :
      //         if ((StepProfile.NumberOfSteps  == 1) && (CurrentMashStep ==1)) {
      //            lcd.setCursor(11,1); lcd.print("/");  lcd.print(StepProfile.Time[0]); lcd.print("\'");
      //         } else {
      if (CurrentMashStep <= StepProfile.NumberOfSteps + 2) {
        Minutes =  (int) ((Now - TimeMashStepStarted)         / 60 );
        Seconds =  (int) ((Now - TimeMashStepStarted) - (Minutes * 60));
        DisplayATime(12, Minutes, Seconds);
        if (CurrentMashStep <= StepProfile.NumberOfSteps+1) {
          lcd.setCursor(17, 1); lcd.print("/"); lcd.print(StepProfile.Time[CurrentMashStep - 1]);
        }
      }
      //         }
      break;
    case Boil :
      // aqui!
      if (CurrentBoilStep == BoilOn) {
        lcd.setCursor(12, 1);
        //Serial.print("HopNumber: "); Serial.println(HopNumber);
        Aux = HopNumber;
        if (WaitForOkButton && (Aux > 1))
          Aux --;  // adjusts hopnumber not to screw display
        if (HopNumber < BoilProfile.NumberOfHops) { // there's still hop(s)/whirflock to add
           Now = (BoilProfile.MinutesTotal * 60 - BoilProfile.TimeOfHop[Aux] * 60)   // gets hop n time to end
                 - (millis() / 1000 - CurrentBoilStepStarted);                       // time elapsed since boilOn started.
           if (HopNumber == BoilProfile.WhichIsWhirflock) {   // it's whirflock, not a hop
              if (Now < 0) {
                 Now = (Now * -1)-1;  // compiler didn't like abs function!
                 lcd.print("w+: ");
              } else {
                 lcd.print(" w: ");          
              }
           } else {   // hop time
#ifdef ENGLISH
              lcd.print("h");
#else
              lcd.print("L");
#endif                
              if (Now < 0) {
                 Now = (Now * -1)-1;  // compiler didn't like abs function!
                 lcd.print("+: ");
              } else {
                 lcd.print(Aux + 1); lcd.print(": ");
              }
           }
           Minutes =  (int) ((Now)        / 60) ;
           Seconds =  (int) ((Now) - (Minutes * 60));
           DisplayATime(15, Minutes, Seconds);
        } else {
            lcd.print(F("        "));
        }
      }
      break;
  }
}

float GetONE_WIRE_BUSTemperature() {
  ONE_WIRE_BUS.reset();
  ONE_WIRE_BUS.select(addr);
  ONE_WIRE_BUS.write(0x44, 1);  // start conversion, with parasite power on at the end
  delay(800);         // maybe 750ms is enough, maybe not

  ONE_WIRE_BUS.reset();
  ONE_WIRE_BUS.select(addr);
  ONE_WIRE_BUS.write(0xBE);     // Read Scratchpad

  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ONE_WIRE_BUS.read();
    //Serial.print(data[i], HEX);
    //Serial.print(" ");
  }

  // Convert the data to Current temperature because the result is a 16 bit signed integer, it should
  // be stored to an "int16_t" type, which is always 16 bits  even when compiled on a 32 bit processor.
  int16_t raw = (data[1] << 8) | data[0];
  if (type_s) {
    raw = raw << 3; // 9 bit resolution default
    if (data[7] == 0x10) {                      // "count remain" gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } else {
    byte cfg = (data[4] & 0x60);
    // at lower res, the low bits are undefined, so let's zero them
    if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    //// default is 12 bit resolution, 750 ms conversion time
  }
  return (float)raw / 16.0;
}

void DisplayTemperature() {
  Now     = millis() / 1000;
  if (Now - HalfSecondAgo > 0.3) {
    HalfSecondAgo = Now;
#ifdef TEST    // A 5K potentiometer is used to mimic the sensor
    Aux = analogRead (SensorTemp);
    CurrentTemperature = (map((Aux), 1000, 0, 0, 100));
#else
    CurrentTemperature = GetONE_WIRE_BUSTemperature();  // a digital read, first sensor in the bus.
#endif
    /* All code is to work with Celsius
       If you want, change sensors funcion by sensors.getTempFByIndex(0) to read Fahreinheit
       and adapt code if necessary.
    */
    lcd.setCursor( 0, 2);
    lcd.print("T:");
    if (CurrentTemperature < 10)
      lcd.print("0");
    lcd.print(CurrentTemperature, 1); lcd.write(B11011111); lcd.print("C");
    switch (CurrentStage) {
      case Prepare :
        lcd.print("/ ");
        lcd.print(StepProfile.InitialTemperature); lcd.write(B11011111); lcd.print("C");
        if ((CurrentTemperature) > StepProfile.InitialTemperature + TemperatureTolerance)
          RedAlert = true ;
        else RedAlert = false;
        if ((CurrentTemperature) < StepProfile.InitialTemperature - TemperatureTolerance)
          YellowAlert = true ;
        else YellowAlert = false;
        break;
      case Mash :
        if (WaitForOkButton) {
          if (CurrentMashStep <= StepProfile.NumberOfSteps) {
            lcd.setCursor(16, 2); lcd.print(StepProfile.Temperature[CurrentMashStep]);
            if ((CurrentTemperature)  > StepProfile.Temperature[CurrentMashStep] + TemperatureTolerance)
              RedAlert = true ;
            else RedAlert = false;
            if ((CurrentTemperature)  < StepProfile.Temperature[CurrentMashStep] - TemperatureTolerance)
              YellowAlert = true ;
            else YellowAlert = false;
          } else {
            lcd.setCursor(16, 2); lcd.print(StepProfile.Temperature[CurrentMashStep - 2]);
            if ((CurrentTemperature)  > 76 + TemperatureTolerance)
              RedAlert = true ;
            else RedAlert = false;
            if ((CurrentTemperature)  < 76 - TemperatureTolerance)
              YellowAlert = true ;
            else YellowAlert = false;
          }
        } else {
          lcd.setCursor(16, 2); lcd.print(StepProfile.Temperature[CurrentMashStep - 1]);
          if ((CurrentTemperature)  > StepProfile.Temperature[CurrentMashStep - 1] + TemperatureTolerance)
            RedAlert = true ;
          else RedAlert = false;
          if ((CurrentTemperature)  < StepProfile.Temperature[CurrentMashStep - 1] - TemperatureTolerance)
            YellowAlert = true ;
          else YellowAlert = false;
        }
        lcd.setCursor(18, 2); lcd.write(B11011111); lcd.print("C");
        lcd.setCursor(13, 2); lcd.print(" / ");
        break;
      case Lauther :
        lcd.setCursor(13, 2); lcd.print(" / 76"); lcd.write(B11011111); lcd.print("C");
        if ((CurrentTemperature) > 76 + TemperatureTolerance)
          RedAlert = true ;
        else RedAlert = false;
        if ((CurrentTemperature) < 76 - TemperatureTolerance)
          YellowAlert = true ;
        else YellowAlert = false;
        break;
      case Boil :
        if ((CurrentTemperature) < 94) // assuming that it will boil less than 100ºC
          YellowAlert = true ;
        else  YellowAlert = false;
        break;
      case Cool :
        lcd.setCursor(13, 2); lcd.print(" / "); lcd.print(CoolToTemperature); lcd.write(B11011111); lcd.print("C");
        break;
    }
  }
}

int InputOption(int Min, int Max, int Encoder0Pos) {
#ifdef POTENTIOMETER  // using a potentiometer
  // Current has no use when reading a potentiometer.
  Aux = analogRead (Potentiometer);
  return (map((Aux), 5, 890, Min, Max)); // converts potentiometer read to value, adjusted for a 5K linear potentiometer
#else
  // http://henrysbench.capnfatz.com/henrys-bench/arduino-sensors-and-input/keyes-ky-040-arduino-rotary-encoder-user-manual/
  i = digitalRead(Encoder0PinA);
  if ((Encoder0PinALast == LOW) && (i == HIGH)) {
    if (digitalRead(Encoder0PinB) == LOW) {
      if (Encoder0Pos > Min)
        Encoder0Pos--;
      else
        Encoder0Pos = Max;
    } else {
      if (Encoder0Pos < Max)
        Encoder0Pos++;
      else
        Encoder0Pos = Min;
    }
  }
  Encoder0PinALast = i;
  return (Encoder0Pos);
#endif
}

void ProcessChooseMash() {
  const int NumberOfProfiles = 7;
  

  MashOption--;
  LastOption = MashOption - 2;
  CleanDisplay;
  Encoder0PinALast = LOW;

  lcd.setCursor( 0, 0);
#ifdef ENGLISH
  lcd.print(F("Select mash profile:"));
#else
  lcd.print(F("Selecione o perfil: "));
#endif
  do {
    MashOption = InputOption(1, NumberOfProfiles, MashOption);

    // steps template - position: 01234561789ABCDEFGHIJ
    //                            50C/20'+64C/30'+70/30
    if (MashOption != LastOption) {
      switch (MashOption) {
        case 1 :
          lcd.setCursor( 0, 1); lcd.print(F("TM 1 step light body"));
          lcd.setCursor( 0, 2); lcd.print(F("64C/75'             "));
          break;
        case 2 :
          lcd.setCursor( 0, 1); lcd.print(F("TM 1 step medium bdy"));
          lcd.setCursor( 0, 2); lcd.print(F("67C/60'             "));
          break;
        case 3 :
          lcd.setCursor( 0, 1); lcd.print(F("TM 1 step full body "));
          lcd.setCursor( 0, 2); lcd.print(F("69C/40'             "));
          break;
        case 4 :
          lcd.setCursor( 0, 1); lcd.print(F("TM 2 step light body"));
          lcd.setCursor( 0, 2); lcd.print(F("50C/30'+64C/75'     "));
          break;
        case 5 :
          lcd.setCursor( 0, 1); lcd.print(F("TM 2 step medium bdy"));
          lcd.setCursor( 0, 2); lcd.print(F("50C/30'+67C/45'     "));
          break;
        case 6 :
          lcd.setCursor( 0, 1); lcd.print(F("TM 2 step full body "));
          lcd.setCursor( 0, 2); lcd.print(F("50C/30'+69C/30'     "));
          break;
        case 7 :
          lcd.setCursor( 0, 1); lcd.print(F("TM 3 step medium bdy"));
          lcd.setCursor( 0, 2); lcd.print(F("50C/20'+62C/20+67/50"));
          break;
      }
      //lcd.setCursor( 2,2); lcd.print("C/");
      lcd.setCursor( 3, 3); lcd.print(MashOption); lcd.print("/"); lcd.print(NumberOfProfiles); lcd.print(" Ok?");
      // Aux = digitalRead(OkButton);
    }
    LastOption = MashOption;
    Aux = digitalRead (OkButton);
  } while (Aux); // OK button pressed goes to false/low/zero

  // initiale fields
  StepProfile.Temperature[0] =  0;  StepProfile.Time[0] =  0;
  StepProfile.Temperature[1] =  0;  StepProfile.Time[1] =  0;
  StepProfile.Temperature[2] =  0;  StepProfile.Time[2] =  0;
  StepProfile.Temperature[3] =  0;  StepProfile.Time[3] =  0;

  switch (MashOption) {
    case 1 : {
        StepProfile.Name = F("TM 1 step light body");
        StepProfile.NumberOfSteps = 1;
#ifdef TEST
        StepProfile.InitialTemperature = 30;  // just for initial tests.
        StepProfile.Time[0] = 1;
#else
        StepProfile.InitialTemperature = 68;
        StepProfile.Time[0] = 75;
#endif
        StepProfile.Temperature[0] = 64;
        break;
      }
    case 2 : {
        StepProfile.Name = F("TM 1 step medium bdy");
        StepProfile.NumberOfSteps = 1;
        StepProfile.InitialTemperature = 71;
        StepProfile.Temperature[0] = 67;  StepProfile.Time[0] = 60;
        break;
      }
    case 3 : {
        StepProfile.Name = F("TM 1 step full body ");
        StepProfile.NumberOfSteps = 1;
        StepProfile.InitialTemperature = 73;
        StepProfile.Temperature[0] = 69;  StepProfile.Time[0] = 40;
        break;
      }
    case 4 :
      StepProfile.Name = F("TM 2 step light body");
      StepProfile.NumberOfSteps = 2;
      StepProfile.InitialTemperature = 55;
      StepProfile.Temperature[0] = 50;  StepProfile.Time[0] = 30;
      StepProfile.Temperature[1] = 64;  StepProfile.Time[1] = 75;
      break;
    case 5 :
      StepProfile.Name = F("TM 2 step medium bdy");
      StepProfile.NumberOfSteps = 2;
      StepProfile.InitialTemperature = 54;
      StepProfile.Temperature[0] = 50;  StepProfile.Time[0] = 30;
      StepProfile.Temperature[1] = 67;  StepProfile.Time[1] = 45;
      break;
    case 6 :
      StepProfile.Name = F("TM 2 step full body ");
      StepProfile.NumberOfSteps = 2;
      StepProfile.InitialTemperature = 54;
      StepProfile.Temperature[0] = 50;  StepProfile.Time[0] = 30;
      StepProfile.Temperature[1] = 69;  StepProfile.Time[1] = 30;
      break;
    case 7 :
    case 8 :
      StepProfile.Name = F("TM 3 step medium bdy");
      StepProfile.NumberOfSteps = 3;
#ifdef TEST
      StepProfile.InitialTemperature = 30;  // just for initial tests.
      StepProfile.Temperature[0] = 50;  StepProfile.Time[0] = 1;
      StepProfile.Temperature[1] = 62;  StepProfile.Time[1] = 2;
      StepProfile.Temperature[2] = 67;  StepProfile.Time[2] = 1;
#else
      StepProfile.InitialTemperature = 54;
      StepProfile.Temperature[0] = 50;  StepProfile.Time[0] = 20;
      StepProfile.Temperature[1] = 62;  StepProfile.Time[1] = 25;
      StepProfile.Temperature[2] = 67;  StepProfile.Time[2] = 50;
#endif
      break;

  }
  // set mash out temperature and time:
  // MashOutTemperature  = 76;
  // MashOutTime         =  1;  defined at program's start
  StepProfile.Temperature[StepProfile.NumberOfSteps] = MashOutTemperature;  
  StepProfile.Time       [StepProfile.NumberOfSteps] = MashOutTime;
}

void ProcessChooseBoil() {
  const int NumberOfBoilProfiles = 18;

  BoilOption--;
  if (BoilOption == 0)
     BoilOption = 1;
  LastOption = BoilOption - 2;
  Encoder0PinALast = LOW;

  CleanDisplay;
  lcd.setCursor( 0, 0);
#ifdef ENGLISH
  lcd.print(F("Select boil profile"));
#else
  lcd.print(F("Selecione boil:    "));
#endif
  do {
    BoilOption = InputOption(1, NumberOfBoilProfiles, BoilOption);
      
      if (BoilOption != LastOption) {
        switch (BoilOption) {
          case 1 :
            lcd.setCursor( 0, 1); lcd.print(F(" 60 min, 1 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'                "));
            break;
          case 2 :
            lcd.setCursor( 0, 1); lcd.print(F(" 60 min, 2 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'   0'           "));
            break;
          case 3 :
            lcd.setCursor( 0, 1); lcd.print(F(" 60 min, 2 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'   5'           "));
            break;
          case 4 :
            lcd.setCursor( 0, 1); lcd.print(F(" 60 min, 3 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  10'   0'      "));
            break;
          case 5 :
            lcd.setCursor( 0, 1); lcd.print(F(" 60 min, 3 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  10'   5'      "));
            break;
          case 6 :
            lcd.setCursor( 0, 1); lcd.print(F(" 60 min, 3 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'   0'      "));
            break;
          case 7 :
            lcd.setCursor( 0, 1); lcd.print(F(" 60 min, 3 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'   5'      "));
            break;
          case 8 :
            lcd.setCursor( 0, 1); lcd.print(F(" 60 min, 3 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'   10'     "));
            break;
          case 9 :
            lcd.setCursor( 0, 1); lcd.print(F(" 75 min, 2 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  15'           "));
            break;
          case 10 :
            lcd.setCursor( 0, 1); lcd.print(F(" 75 min, 2 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'           "));
            break;
          case 11:
            lcd.setCursor( 0, 1); lcd.print(F(" 75 min, 3 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'   0'      "));
            break;
          case 12 :
            lcd.setCursor( 0, 1); lcd.print(F(" 90 min, 2 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  15'           "));
            break;
          case 13 :
            lcd.setCursor( 0, 1); lcd.print(F(" 90 min, 2 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'           "));
            break;
          case 14 :
            lcd.setCursor( 0, 1); lcd.print(F(" 90 min, 3 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'   0'      "));
            break;
          case 15 :
            lcd.setCursor( 0, 1); lcd.print(F("120 min, 2 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  15'           "));
            break;
          case 16 :
            lcd.setCursor( 0, 1); lcd.print(F("120 min, 2 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'           "));
            break;
          case 17 :
            lcd.setCursor( 0, 1); lcd.print(F("120 min, 3 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'   0'      "));
            break;
          case 18 :
            lcd.setCursor( 0, 1); lcd.print(F("120 min, 4 hops     "));
            lcd.setCursor( 0, 2); lcd.print(F(" 60'  20'   15'  0' "));
            break;
        }
        if (BoilOption < 10) {
          lcd.setCursor( 2, 3); lcd.print(" ");
          //lcd.setCursor( 3,3);
        } else
          lcd.setCursor( 2, 3);
        lcd.print(BoilOption);
        //lcd.setCursor( 4,3);
        lcd.print("/");
        //lcd.setCursor( 5,3);
        lcd.print(NumberOfBoilProfiles);
        lcd.print(" Ok?");
      }
      LastOption = BoilOption;
      Aux = digitalRead (OkButton);
    } while (Aux); // OK button pressed goes to false/low/zero

    // to avoid repeated code
    BoilProfile.TimeOfHop[1] = -1; // -1 indicates no hop at this time
    BoilProfile.TimeOfHop[2] = -1;
    BoilProfile.TimeOfHop[3] = -1;
    BoilProfile.TimeOfHop[4] = -1;
    
    switch (BoilOption) {
      case 1 :
         // 60 min, 1 hops
         // hop at 60'
         BoilProfile.MinutesTotal    = 60;
         BoilProfile.NumberOfHops    =  2;
         BoilProfile.TimeOfHop[0]    = 60;
         BoilProfile.TimeOfHop[1]    = WhirflockTime; // WhirflockTime is defined at code top and should be between 11 and 14. 
         BoilProfile.WhichIsWhirflock=  1;            // if u use different value, change logic here.
         break;
      case 2 :
         //60 min, 2 hops
         // hops at 60'   0'
         BoilProfile.MinutesTotal    = 60;
         BoilProfile.NumberOfHops    =  3;
         BoilProfile.TimeOfHop[0]    = 60; 
         BoilProfile.TimeOfHop[1]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  1;
         BoilProfile.TimeOfHop[2]    =  0;
         break;
      case 3 :
         // 60 min, 2 hops
         // hops at 60'   5'
         BoilProfile.MinutesTotal    = 60;
         BoilProfile.NumberOfHops    =  3;
         BoilProfile.TimeOfHop[0]    = 60;
         BoilProfile.TimeOfHop[1]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  1;        
         BoilProfile.TimeOfHop[2]    =  5;
         break;
      case 4 :
         //60 min, 3 hops
         // hops at 60'  10'   0'
         BoilProfile.MinutesTotal    = 60;
         BoilProfile.NumberOfHops    =  4;
         BoilProfile.TimeOfHop[0]    = 60;
         BoilProfile.TimeOfHop[1]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  1;        
         BoilProfile.TimeOfHop[2]    = 10;
         BoilProfile.TimeOfHop[3]    =  0;
         break;
      case 5 :
         // 60 min, 3 hops
         // hops at 60'  10'   5'
         BoilProfile.MinutesTotal    = 60;
         BoilProfile.NumberOfHops    =  4;
         BoilProfile.TimeOfHop[0]    = 60;
         BoilProfile.TimeOfHop[1]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  1;        
         BoilProfile.TimeOfHop[2]    = 10;
         BoilProfile.TimeOfHop[3]    =  5;
         break;
      case 6 :
         // 60 min, 3 hops
         // hops at  60'  20'   0'
         BoilProfile.MinutesTotal    = 60;
         BoilProfile.NumberOfHops    =  4;
         BoilProfile.TimeOfHop[0]    = 60;
         BoilProfile.TimeOfHop[1]    = 20;
         BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
         BoilProfile.TimeOfHop[3]    =  0;
         break;
      case 7 :
          // 60 min, 3 hops
          // hops at  60'  20'   5'
          BoilProfile.MinutesTotal    = 60;
          BoilProfile.NumberOfHops    =  4;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 20;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          BoilProfile.TimeOfHop[3]    =  5;
          break;
       case 8 :
          // 60 min, 3 hops
          // hops at  60'  20'   10'
          BoilProfile.MinutesTotal    = 60;
          BoilProfile.NumberOfHops    =  4;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 20;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          BoilProfile.TimeOfHop[3]    = 10;
          break;
       case 9 :
          // 75 min, 2 hops
          // hops at 60'  15'
          BoilProfile.MinutesTotal    = 75;
          BoilProfile.NumberOfHops    =  3;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 15;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          break;
        case 10 :
          // 75 min, 2 hops
          // hops at  60'  20'
          BoilProfile.MinutesTotal    = 75;
          BoilProfile.NumberOfHops    =  3;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 20;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          break;
       case 11:
          // 75 min, 3 hops
          // hops at  60'  20'   0'
          BoilProfile.MinutesTotal    = 75;
          BoilProfile.NumberOfHops    =  4;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 20;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          BoilProfile.TimeOfHop[3]    =  0;
          break;
       case 12 :
          // 90 min, 2 hops
          // hops at  60'  15'
          BoilProfile.MinutesTotal    = 90;
          BoilProfile.NumberOfHops    =  3;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 15;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          break;
       case 13 :
          // 90 min, 2 hops
          // hops at  60'  20'
          BoilProfile.MinutesTotal    = 90;
          BoilProfile.NumberOfHops    =  3;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 20;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          break;
       case 14 :
          // 90 min, 3 hops
          // hops at  60'  20'   0'
          BoilProfile.MinutesTotal    = 90;
          BoilProfile.NumberOfHops    =  4;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 20;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          BoilProfile.TimeOfHop[3]    =  0;
          break;
       case 15 :
          // 120 min, 2 hops
          // hops at 60'  15'
          BoilProfile.MinutesTotal    =120;
          BoilProfile.NumberOfHops    =  3;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 15;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          break;
       case 16 :
          // 120 min, 2 hops
          // hops at  120'  60'  20'
          BoilProfile.MinutesTotal    =120;
          BoilProfile.NumberOfHops    =  3;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 20;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;        
          break;
       case 17 :
          // 120 min, 3 hops
          // hops at  60'  20'   0'
          BoilProfile.MinutesTotal    =120;
          BoilProfile.NumberOfHops    =  4;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 20;
          BoilProfile.TimeOfHop[2]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  2;                 
          BoilProfile.TimeOfHop[3]    =  0;
          break;
        case 18 :
          // 120 min, 4 hops
          // hops at  60'  20'   15'  0'
#ifdef TEST
          BoilProfile.MinutesTotal    =  6;
          BoilProfile.NumberOfHops    =  5;
          BoilProfile.TimeOfHop[0]    =  5;
          BoilProfile.TimeOfHop[1]    =  4;
          BoilProfile.TimeOfHop[2]    =  3;
          BoilProfile.TimeOfHop[3]    =  2;
          BoilProfile.TimeOfHop[4]    =  1; BoilProfile.WhichIsWhirflock=  4;        

#else
          BoilProfile.MinutesTotal    =120;
          BoilProfile.NumberOfHops    =  5;
          BoilProfile.TimeOfHop[0]    = 60;
          BoilProfile.TimeOfHop[1]    = 20;
          BoilProfile.TimeOfHop[2]    = 15;
          BoilProfile.TimeOfHop[3]    = WhirflockTime; BoilProfile.WhichIsWhirflock=  3;        
          BoilProfile.TimeOfHop[4]    =  0;
#endif
          break;
      }
}

void ProcessChoose() {
  delay(200);
  CleanDisplay();
  
  ProcessChooseMash();
  delay(1000);

  ProcessChooseBoil();
   CurrentStage    = Prepare;
   PreviousStage  = Choose ;
#ifdef TEST
  CurrentStage       = Boil;  // jumps to boil stage, for debuggin'
#endif
}
/*
void ProcessPrepare() {
  if (CurrentStage != PreviousStage) {
    CleanDisplay();
    PreviousStage = CurrentStage;
    MessageStage(Prepare);
    TimeStageStarted = millis() / 1000;
    TurnHeat(On);

    Message( 5, true); //"Heat the wather"
    WaitForOkButton    = false;
    OkButtonWasPressed = false;
  }
  if ((CurrentTemperature >= StepProfile.InitialTemperature) && (WaitForOkButton == false)) {
    TurnHeat (Off);
    TurnMotor(On );
    Message( 6, true); //"Add grains  (ok)"
    WaitForOkButton = true;
  }
  if (WaitForOkButton && OkButtonWasPressed)
    CurrentStage++; //ok, let's go to next stage
}
*/
void ProcessMash() {
  if (CurrentStage != PreviousStage) {
    CleanDisplay();
    PreviousStage = CurrentStage;
    TimeStageStarted = millis() / 1000;
    CurrentMashStep = 1;
    TimeMashStepStarted = millis() / 1000;
    WaitForOkButton    = false;
    OkButtonWasPressed = false;
    MessageStage(Mash);
    Message(0, false);
  }

  // Current mash step time is over, tests once; add 1 more step, the mash out

  // same stage, next step
  if (     (WaitForOkButton == false)
           && (((millis() / 1000) - TimeMashStepStarted) > StepProfile.Time[CurrentMashStep - 1] * 60)
           && (CurrentMashStep <= StepProfile.NumberOfSteps + 2))
    if (CurrentMashStep == StepProfile.NumberOfSteps + 1) {
      TurnMotor(Off);
      TurnHeat (Off);
      CurrentMashStep++;
      Message( 7, true); //"Mash complete (Ok)"
      WaitForOkButton = true;
    } else {
      TurnMotor(On);
      TurnHeat (On);
      Message( 8, true); //"Warm up til ramp (Ok)"
      WaitForOkButton = true;
    }

  // same step, but temperature too low.
  if (WaitForOkButton == false) {
    if (CurrentTemperature < StepProfile.Temperature[CurrentMashStep - 1]) {
      if (!HeatIsOn) {
        TurnMotor(On);
        TurnHeat (On);
      }
    } else {
      if (HeatIsOn) {
        TurnMotor(Off);
        TurnHeat (Off);
      }
    }
  } else { // go to next mash step, and it's temperature has been reached - no need to wait for OK press
    if ((CurrentMashStep < StepProfile.NumberOfSteps + 1) && (CurrentTemperature >= StepProfile.Temperature[CurrentMashStep])) {
      TurnMotor(Off);
      TurnHeat (Off);
      CurrentMashStep++;
      lcd.setCursor(15, 0);
      if (CurrentMashStep  <= StepProfile.NumberOfSteps)
        lcd.print(CurrentMashStep);
      else lcd.print("M-out");  // last "step" is mash out, it's not choosen, but defined in choose function.
      WaitForOkButton     = false;
      OkButtonWasPressed  = false;
      TimeMashStepStarted = millis() / 1000;
      lcd.setCursor(0, 1);  lcd.print(F("                   ")); // best place in the code to clean line in display.
    }
  }
  if (WaitForOkButton && OkButtonWasPressed) {
    if (CurrentMashStep > StepProfile.NumberOfSteps + 1) {
      CurrentStage++; //ok, let's go to next stage
      TurnMotor(Off);
      TurnHeat (Off);
    } else { // same stage, next step.
      Message( 0, false);  // cleans message
      CurrentMashStep++;
      lcd.setCursor(15, 0);
      if (CurrentMashStep  <= StepProfile.NumberOfSteps)
        lcd.print(CurrentMashStep);
      else lcd.print("M-out");
      WaitForOkButton     = false;
      OkButtonWasPressed  = false;
      TurnMotor(On);
      TurnHeat (On);
      lcd.setCursor(0, 1);  lcd.print("                   "); // best place in the code to clean line in display. :-)
      TimeMashStepStarted = millis() / 1000;
      if (CurrentTemperature < StepProfile.Temperature[CurrentMashStep - 1]) {
        TurnMotor(On);
        TurnHeat (On);
      }
    }
  }
}

void ProcessLauther() {
  if (CurrentStage != PreviousStage) {
    CleanDisplay();
    PreviousStage = CurrentStage;
    MessageStage(Lauther);
    TimeStageStarted   = millis() / 1000;
    WaitForOkButton    = false ;
    OkButtonWasPressed = false;
    TurnPump(Recirculate, On);
    Message( 9, true); //"Recirculate"
    lcd.setCursor(11, 1); lcd.print("/"); lcd.print(10); lcd.print("\'");
  }
#ifdef TEST
  if ((WaitForOkButton == false) &&  (((millis() / 1000) - TimeStageStarted) > 1 * 60)) {
#else
  if ((WaitForOkButton == false) &&  (((millis() / 1000) - TimeStageStarted) > ClarificationTime * 60)) {
#endif
    TurnPump(Recirculate, Off);
    TurnPump(Sparge     , On );
    delay(100);
    lcd.setCursor(11, 1); lcd.print("    ");
    delay (100);
    WaitForOkButton = true;
  }
  if (WaitForOkButton && OkButtonWasPressed) {
    TurnPump(Sparge     , Off );
    CurrentStage++; //ok, let's go to next stage
  }

}

void ProcessBoil() {
  if (CurrentStage != PreviousStage) {
    CleanDisplay();
    PreviousStage = CurrentStage;
    MessageStage(Boil);
    TimeStageStarted = millis() / 1000; // saves the time when boil up started
    CurrentBoilStepStarted = TimeStageStarted;
    WaitForOkButton    = true;
    OkButtonWasPressed = false;
    RedAlert           = false;
    YellowAlert        = false;
    CurrentBoilStep    = BoilUp;
    TurnHeat(On);
    HopNumber    = 0;
    BoilTotalDelaySeconds = 0;
    BoilDelaySeconds      = 0;
    Message(10, true); //"Heat to boiling!(ok)"
    lcd.setCursor(13, 2); lcd.print(" ");
  }
  switch (CurrentBoilStep) {
    case BoilUp :
      //Serial.println("BoilUp"); delay (1000);
      if (WaitForOkButton && OkButtonWasPressed) {
        CurrentBoilStep++;
        CurrentBoilStepStarted = millis() / 1000;  // saves the time when boil on started
        TimeStageStarted = CurrentBoilStepStarted; // it makes time restart when boil is set to on
        WaitForOkButton = false;
        Message( 0, false); // cleans message
      }
      break;

    case BoilOn :
      // Proccess Hops:
      if (!WaitForOkButton &&                                                                                                // If this is the first time for this hop (not WaitForOk)
          (HopNumber < BoilProfile.NumberOfHops) &&                                                                           // and There's still hops to add
          (((millis() / 1000) - CurrentBoilStepStarted) + BoilProfile.TimeOfHop[HopNumber] * 60 >= (BoilProfile.MinutesTotal * 60) )) { // and it's time to add a hop/whirflock (BoilMinutesTotal - TimeElapsed) <= time to add Hop
         if (HopNumber == BoilProfile.WhichIsWhirflock) {    // it's not a hop, it's the whirflock.
             Message(13, true); //"Add whirflock (Ok)"
         } else {
             StrAddHop  = HopNumber + 1;    // StrAddHop is a string used to show message 12
             StrAddHop += "/";
             StrAddHop += BoilProfile.TimeOfHop[HopNumber];
             StrAddHop += "\'(OK)";
             Message(12, true); //"Add hop n/mm'(Ok)"
         }
         WaitForOkButton    = true;
         OkButtonWasPressed = false;
      }
      // boil total time is over:
      if (!WaitForOkButton &&
          (((millis() / 1000) - CurrentBoilStepStarted) > ((BoilProfile.MinutesTotal * 60)))) { // Time of Boil (boilOn) is greater than Total Boil time for the profile + delays in the hop additions
        CurrentBoilStep++; //ok, let's go to next step
        CurrentBoilStepStarted = millis() / 1000; // saves the time when boil ok
        WaitForOkButton    = true;
        OkButtonWasPressed = false;
        TurnHeat(Off);
        Message(11, true); //"Boil completed (Ok)"
      }

      if (WaitForOkButton && OkButtonWasPressed) {
        BoilDelaySeconds   = ((millis() / 1000) - CurrentBoilStepStarted - (BoilProfile.MinutesTotal - BoilProfile.TimeOfHop[HopNumber]) * 60);
        BoilTotalDelaySeconds += BoilDelaySeconds;
        HopNumber++;
        CurrentBoilStepStarted = CurrentBoilStepStarted + BoilDelaySeconds;
        if (BoilDelaySeconds > 60) {
          Message(19, false); // "Delayed      minutes"
          lcd.setCursor(9, 3);  lcd.print(BoilDelaySeconds / 60);
          delay(700);
        }
        MessageStage(Boil);
        WaitForOkButton    = false;
        OkButtonWasPressed = false;
        Message(0, false); // cleans line 4
      }
      break;

    case BoilOk :
      if (WaitForOkButton && OkButtonWasPressed) {
        Message(14, true); //"Rest for 10 min."
        CurrentBoilStep++; //ok, let's go to next step
        CurrentBoilStepStarted = millis() / 1000; // saves the time when boil ok
        TimeStageStarted = CurrentBoilStepStarted;
        WaitForOkButton    = false;
        OkButtonWasPressed = false;
      }
      //delay(100);
      break;

    case BoilRest:
#ifdef TEST
      if (((millis() / 1000) - CurrentBoilStepStarted) > 67) { // for development purposes
#else
      if (((millis() / 1000) - CurrentBoilStepStarted) > RestTime * 60) { // rest for ten minutes
#endif
        CurrentStage++; //ok, let's go to next stage
      }
      break;
  }
}

void ProcessCool() {
  if (CurrentStage != PreviousStage) {
    CleanDisplay();
    PreviousStage = CurrentStage;
    MessageStage(Cool);
    TimeStageStarted   = millis() / 1000;
    WaitForOkButton    = false ;
    OkButtonWasPressed = false;
    RedAlert           = false;
    YellowAlert        = false;
    Message(15, true); //"Cool the wort "
  }
  if ((CurrentTemperature <= CoolToTemperature) && !WaitForOkButton) {
    WaitForOkButton = true;
    Message (16, false); //"Remove chiller (OK)"
  }
  if (WaitForOkButton && OkButtonWasPressed) {
    CurrentStage++; //ok, let's go to next stage
  }
}

void ProcessWhirlpool() {
  if (CurrentStage != PreviousStage) {
    CleanDisplay();
    PreviousStage = CurrentStage;
    MessageStage(Whirlpool);
    TimeStageStarted   = millis() / 1000;
    WaitForOkButton    = false ;
    OkButtonWasPressed = false;
    lcd.setCursor(11, 1);  lcd.print("/"); lcd.print(5); lcd.print("\'");
    lcd.setCursor(12, 2);  lcd.print(" ");
    TurnMotor(On);
    TurnPump(Whirlpool, On);
  }
#ifdef TEST
  if (((millis() / 1000) - TimeStageStarted) > 60) { // for development purposes
#else
  if (((millis() / 1000) - TimeStageStarted) >= WhirlpoolTime * 60) {
#endif
    TurnMotor(Off);
    TurnPump (Whirlpool, Off);
    CurrentStage++;
  }

}

void ProcessEnd() {
  if (CurrentStage != PreviousStage) {
    CleanDisplay();
    PreviousStage = CurrentStage;
    TimeStageStarted = millis() / 1000;
    lcd.setCursor(0, 0); lcd.print (ProgramString); // "Sotile Brewing vn.n
#ifdef ENGLISH
    lcd.setCursor(0, 1); lcd.print (F("It took" ));
    lcd.setCursor(0, 2); lcd.print (F("to complete brewing" ));
#else
    lcd.setCursor(0, 1); lcd.print (F("Foram" ));
    lcd.setCursor(0, 2); lcd.print (F("em todo o processo." ));
#endif
    Minutes =  (int) ((TimeStageStarted)        / 60) ;
    Seconds =  (int) ((TimeStageStarted) - (Minutes * 60));
    DisplayATime(8, Minutes, Seconds);
    lcd.setCursor(0, 3); lcd.print ("(ESC)" );
    WaitForOkButton = false ;
  }
}

/*ProcessCancelButton();

   This function should allow user to:
   - Restart whole proccess
   - Restart current Stage
   - Restart current step within a stage

   This can be done changing values of status variables like
        CurrentStage,      PreviousStage,
        CurrentMashStep,   CurrentBoilStep,
*/

//------------------------------------------------------ ESC button Routines -----------------------------

void EscButtonChooseOption() {
  int MinOption, MaxOption;
  boolean StepHasSubsteps;
  //Serial.println("Entrou ESC - choose");
  CleanDisplay();
  lcd.setCursor( 0, 0);
#ifdef ENGLISH
  lcd.print(F("(!) Select:"));
#else
  lcd.print(F("(!) Selecione:"));
#endif

  switch (CurrentStage) {
    case Choose:
    case Prepare:
    case Lauther:
    case Cool:
    case Whirlpool:
    case End:        StepHasSubsteps = false;
      MaxOption = 2;    break;
    case Mash:
    case Boil:       StepHasSubsteps = true;
      MaxOption = 3;    break;
  }
  if (SoundAlarm && !SoundAlarmCanceled)
    MinOption = 0;  // option 0 = mute the alarm
  else MinOption = 1;  // will not show the option to mute an alarm that is not on!
  Option     = MinOption;
  LastOption = Option - 1;
  //Serial.print("Option:    "); Serial.println(Option);
  //Serial.print("MinOption: "); Serial.println(MinOption);
  //Serial.print("MaxOption: "); Serial.println(MaxOption);
  //Serial.print("HasSubStep:"); Serial.println(StepHasSubsteps);
  do {
    Option = InputOption(MinOption, MaxOption, Option);
    if (Option != LastOption) {
      switch (Option) {
#ifdef ENGLISH
        case 0 : lcd.setCursor( 0, 2); lcd.print(F(" Mute the alarm     ")); break;
        case 1 : lcd.setCursor( 0, 2); lcd.print(F(" Go to step ...     ")); break;
        case 2 : if (!StepHasSubsteps) {
            Option++;
          } else {
            lcd.setCursor( 0, 2); lcd.print(F(" Go to substep ...  ")); break;
          }
        case 3 : lcd.setCursor( 0, 2); lcd.print(F(" Exit with no change")); break;
#else
        case 0 : lcd.setCursor( 0, 2); lcd.print(F(" Desliga o alarme   ")); break;
        case 1 : lcd.setCursor( 0, 2); lcd.print(F(" Vai para a etapa...")); break;
        case 2 :
        case 3 : if (Option == 2 && StepHasSubsteps) {
            lcd.setCursor( 0, 2); lcd.print(F(" Vai p/ sub-etapa...")); break;
          } else {
            lcd.setCursor( 0, 2); lcd.print(F(" Sai sem alteracao  "));
            lcd.setCursor(15, 2); lcd.print(char(0));          // writes a  "ç"
            lcd.print(char(1)); break;    // writes an "ã"
          }
#endif
      }
      lcd.setCursor(10, 3); lcd.print(" Ok?");
    }
    LastOption = Option;
    Aux = digitalRead (OkButton);
  }  while (Aux); // OK button pressed goes to false/low/zero
  //Serial.print("Option apos:"); Serial.println(Option);
  if (!StepHasSubsteps && Option == 2)
    Option++;  //if no substeps, must set option from 2 to 3 that is "exit with no change"
  //   return Option;
}


void EscButtonChangeStep() {
  CleanDisplay();
  if (HeatIsOn) {
    TurnMotor(Off);
    TurnHeat (Off);
    HeatIsOn = false;
  }

  lcd.setCursor( 0, 0);
#ifdef ENGLISH
  lcd.print(F("(!) Select stage:")); // translate here
  lcd.setCursor( 0, 1); lcd.print(F("Current:")); MessageStageName(CurrentStage);
  lcd.setCursor( 0, 2); lcd.print(F("New   : ")); MessageStageName(CurrentStage);
#else
  lcd.print(F("(!) Selec. estapa:"));
  lcd.setCursor( 0, 1); lcd.print(F("Atual : ")); MessageStageName(CurrentStage);
  lcd.setCursor( 0, 2); lcd.print(F("Nova  : ")); MessageStageName(CurrentStage);
#endif
  lcd.setCursor(10, 3); lcd.print(" Ok?");
  Option     = CurrentStage;
  LastOption = CurrentStage;
  delay (100); // kinda debounce ok button.
  do {
    Option = InputOption(Choose, Whirlpool + 1, Option);
    if (Option != LastOption) {
      lcd.setCursor(8, 2); lcd.print(F("            ")); // clear previous message
      lcd.setCursor(8, 2); MessageStageName(Option);
      LastOption = Option;
    }
    Aux = digitalRead (OkButton);
  }  while (Aux); // OK button pressed goes to false/low/zero
  if (Option == CurrentStage) {
    //Serial.println("exit with no change");
    Message(18, true); // "Exit with no change!"
    delay(1500);
  } else { // a different stage was selected
    switch (CurrentStage) {
      case Lauther :  TurnPump(0, Off); break;
      case Prepare :
      case Mash    :
      case Boil    :  TurnHeat (Off);
                      TurnMotor(Off);  break;
      case Whirlpool: TurnPump(0, Off);
                      TurnMotor(Off);  break;
    }
    SoundAlarmCanceled = false;
    CurrentStage  = Option;
    PreviousStage = CurrentStage - 1; //forces the code to restart selected stage
  }
}

void EscButtonChangeSubStep() {
  CleanDisplay();
  lcd.setCursor( 0, 0);
  /* Mash and Boil have substeps.
     options are get back to the substep, even if it is single
                 or exit without changes
     if mash it will be:
     "now: STAGE STEP TIME
     "new: STAGE step zero
     *** if same step, ask if it's to set reset time or exit without changes

     if boil it will be :
     "now: hop 1/3 time"
     "new: hop n/3 time " -->> rotary encoder hop number - calculate remaining time
     *** if same step, ask if it's to set reset time or exit without changes

    struct StructSteps {
    String Name;
    int    InitialTemperature;
    int    NumberOfSteps;    <<<<<<<<
    int    Temperature[4]; // 3 steps plus mashout
    int    Time[4];        // 3 steps plus mashout
    };

    struct StructBoil {
    int MinutesTotal;
    int NumberOfHops;         <<<<<<<<
    int TimeOfHop[4];  // max of 4 hoppings during boil
    };
  */

#ifdef ENGLISH
  lcd.print(F("(Esc!) Select step: ")); // translate here
  //    Max message size, in English:  01234567890123456789  , considering possible stages are Mash and Boil
  //                      lcd.print(F("Now: Boil  59:00 1/4"));
  lcd.setCursor( 0, 1); lcd.print(F("Now : ")); MessageStageName(CurrentStage);
  lcd.setCursor( 0, 2); lcd.print(F("New : "));
#else
  lcd.print(F("(Esc!) Selec. passo:"));
  //    Max message size, in Portug.:  01234567890123456789  , considering possible stages are Mostura and Fervura
  //                      lcd.print(F("A: Fervura 59:00 1/4"));
  lcd.setCursor( 0, 1); lcd.print(F("A: ")); MessageStageName(CurrentStage);
  lcd.setCursor( 0, 2); lcd.print(F("N: "));
#endif
  lcd.setCursor(17, 1);
  //struct StructSteps StepProfile;
  //struct StructBoil  BoilProfile;

  if (CurrentStage == Mash) {
    lcd.print(CurrentMashStep); lcd.print("/"); lcd.print(StepProfile.NumberOfSteps);
    lcd.setCursor( 0, 3); lcd.print(" <rampa>");
    Option = CurrentMashStep;
  } else { // CurrentStage == Boil
    lcd.print(HopNumber);       lcd.print("/"); lcd.print(BoilProfile.NumberOfHops);
    lcd.setCursor( 0, 3); lcd.print(" <hop>");
    Option = HopNumber;
  }
  lcd.setCursor(10, 3); lcd.print(" Ok?");
  LastOption = Option;

  do {
    Minutes =  (int) ((Now - TimeStageStarted)        / 60);
    Seconds =  (int) ((Now - TimeStageStarted) - (Minutes * 60));
    DisplayATime(11, Minutes, Seconds);

    Option = InputOption(Choose, Whirlpool + 1, Option);
    if (Option != LastOption) {
      lcd.setCursor(11, 2); MessageStageName(CurrentStage);
    }
    LastOption = Option;
    Aux = digitalRead (OkButton);
  }  while (Aux); // OK button pressed goes to false/low/zero

}

void(* resetFunc) (void) = 0; //declare reset function @ address 0
// http://www.instructables.com/id/two-ways-to-reset-arduino-in-software/?ALLSTEPS


void ProcessEscButton() {
  /*
     This function should allow user to:
     - silent alarm buzzer
     - go to step
     - go to substep
     - exit with no change

     This can be done changing values of status variables like
          CurrentStage,      PreviousStage,
          CurrentMashStep,   CurrentBoilStep,
  */
  /*  This code forces an arduino reset if ESC is pressed at end of proccess / stage END
    if (CurrentStage > Whirlpool) // Process already ended
       resetFunc();  //call reset
    // else:
  */
  NumberLastMessage = AuxNumberLastMessage;
  EscButtonChooseOption();
  switch (Option) {
    case 0 : // mute alarm
      SoundAlarmCanceled = true;
      digitalWrite(IntermitentBuzzer, LOW);
      break;
    case 1 : EscButtonChangeStep();    break;
    case 2 : EscButtonChangeSubStep(); break;
    case 3 : break; // Exit with no change
  }
  CleanDisplay();
  MessageStage(CurrentStage);
  //Serial.print("currentstage: "); Serial.println(CurrentStage);
  if (CurrentStage == Mash) {
    //Serial.print("1currentstage: "); Serial.println(CurrentStage);
    lcd.setCursor(15, 0);
    if (CurrentMashStep  <= StepProfile.NumberOfSteps) {
      lcd.print(CurrentMashStep);
    } else
      lcd.print("M-out");
  }
  //Serial.print("NumberLastMessage: ");   Serial.println(NumberLastMessage);
  Message(NumberLastMessage, false);
  TimeLastOkInterrupt = millis();
  OkButtonWasPressed = false;
}

// ################################ SETUP ########################################

void setup() {
  /* Parallel display:
     lcd.begin(20, 4);   // start the library
  */
  //i2c serial display:
  lcd.init();
  lcd.backlight();
  CleanDisplay();
  // starting message
  lcd.setCursor(0, 0); lcd.print (ProgramString); //"Sotile Homebrewing v.1n"
#ifdef ENGLISH
  lcd.setCursor(0, 1); lcd.print (F("     2017 April    "));
  lcd.setCursor(0, 2); lcd.print (F(" by: Ioquir Sotile "));
  lcd.setCursor(0, 3); lcd.print (F("  ...setting up... ")); // nothing! just to keep name and version on the display
#else
  lcd.setCursor(0, 1); lcd.print (F("   Abril de 2017   "));
  lcd.setCursor(0, 2); lcd.print (F(" de: Ioquir Sotile "));
  lcd.setCursor(0, 3); lcd.print (F(" ...configurando..."));
  lcd.createChar(0, Cedilha);  // creates "ç" char
  lcd.createChar(1, ATil   );  // creates "ã" char
  lcd.createChar(2, AAgudo );  // creates "á" char
#endif

#ifdef TEST
  Serial.begin(9600);      // open the serial port at 9600 bps:
#endif

  pinMode(Buzzer,            OUTPUT);
  pinMode(IntermitentBuzzer, OUTPUT);
  pinMode(RGBLedBluePin,     OUTPUT);
  pinMode(RGBLedGreenPin,    OUTPUT);
  pinMode(RGBLedRedPin,      OUTPUT);
  //pinMode(OkButton,          INPUT );
  attachInterrupt(1, debounceOkButtonInterrupt,  CHANGE); //OkButton
  //pinMode(EscButton,         INPUT_PULLUP);
  attachInterrupt(0, debounceEscButtonInterrupt, CHANGE); //EscButton
  pinMode(Encoder0PinA,      INPUT );
  pinMode(Encoder0PinB,      INPUT );

  CurrentStage         = Choose;
  PreviousStage        = CurrentStage;
  MashOption           = 1;
  BoilOption           = 0;
  WaitForOkButton      = false;
  OkButtonWasPressed   = false;
  EscButtonWasPressed  = false;
  Now                  = millis();
  HalfSecondAgo        = Now;
  TimeStageStarted     = Now;
  TimeLastOkInterrupt  = Now;
  TimeLastEscInterrupt = Now;
  RedAlert             = false;
  YellowAlert          = false;

  delay(1500);

  // one wire temperature sensor setup:
  if ( !ONE_WIRE_BUS.search(addr)) {
    //Serial.println("No sensor found - No more addresses.");
    ONE_WIRE_BUS.reset_search();
    delay(250);
    return;
  }
  if (OneWire::crc8(addr, 7) != addr[7]) {
    //Serial.println("CRC is not valid!");
    return;
  }
  //Serial.println();
  // the first ROM byte indicates which chip
  switch (addr[0]) {
    case 0x10:       //Serial.println("  Chip = DS18S20");  // or old DS1820
      type_s = 1;  break;
    case 0x28:       //Serial.println("  Chip = DS18B20");
      type_s = 0;  break;
    case 0x22:      //Serial.println("  Chip = DS1822");
      type_s = 0;  break;
    default:        //Serial.println("Device is not a DS18x20 family device.");
      return;
  }

  Message(17, true); //" Press (OK) to start "
  delay(1000);
  do
    Aux = digitalRead(OkButton);
  while (Aux); // OK button pressed goes to false/low/zero
  CleanDisplay();

}

// ################################ LOOP ########################################

void loop() {

  /*   if (WaitForOkButton){  // see INTERRUPT bellow
         Aux = digitalRead(OkButton);
         //Serial.print("Aux: "); Serial.print(Aux); Serial.print("-- Ok pressed: "); Serial.println(OkButtonWasPressed);
         if (Aux == LOW) {
            OkButtonWasPressed = 1;
         }
     }
  */

  if (EscButtonWasPressed) {
    if (CurrentStage > Choose)
      ProcessEscButton();
    EscButtonWasPressed = false;
  }

  switch (CurrentStage) {
    case Choose    : ProcessChoose   (); break;
    //case Prepare   : ProcessPrepare  (); break;
    //case Mash      : ProcessMash     (); break;
   // case Lauther   : ProcessLauther  (); break;
    //case Boil      : ProcessBoil     (); break;
    //case Cool      : ProcessCool     (); break;
   // case Whirlpool : ProcessWhirlpool(); break;
  //  default        : ProcessEnd      (); break;
  }

  ShowAlert ();

  if ((CurrentStage <= Whirlpool) && (PreviousStage != Choose)) {
    DisplayTimes();
    DisplayTemperature();
  }
}


// ################################ INTERRUPS ########################################

void debounceOkButtonInterrupt() {
  if ((long)(millis() - TimeLastOkInterrupt) >= DebouncingTime     * 500) {
    OkButtonWasPressed  = true;
    TimeLastOkInterrupt = millis();
  }
}

void debounceEscButtonInterrupt() {
  if ((long)(millis() - TimeLastEscInterrupt) >= DebouncingTime     * 500) {
    EscButtonWasPressed = true; // I tried to call ProcessEscButton() from here, but compilator didn't like the idea, so I had to move it to loop.
    TimeLastEscInterrupt = millis();
  }
}
$abcdeabcde151015202530fghijfghij