
/* This program is to measure the remaining amount of fresh water in the campervan. It
 *  counts the pulses from a flow valve (Hall Effect type) and displays the results on
 *  a 64*128 OLED display as a persentage of total amount of water remaining.
 *  after 15 seconds of no use it goes into a screen save routine only showing 1 moving pixle
 *  When power is removed, it saves the current count to EEPROM (only if it's changed)
 *  Every time water flows through the valve the unit wakes up and displays the remaining water
 *  
 *  To establish the initial tank size, completly fill tank with fresh water then press
 *  the "Tank Full" microswitch. Now use the water as per normal until tank is completely
 *  empty. Now push the "Tank Empty" microswitch. This logs the total amount of pulses
 *  that the tank gives. 
 *  Note: From now on the "Tank Empty" microswitch does NOT need to be pressed again.
 *  Now, every time the tank is filled you must press the "Tank Full" micro
 *  
 *  Version 2 = with dirty switch on it prevents writing to EEPROM
 *  
 *  
 * Note: EEPROM memory for my campervan is 
 * EEPROM 02 = 0x7C
 * EEPROM 03 = 0x43
 * This equates to 31811 pulses per 120 litres = 265 pulses per litre
 */
#include <U8glib.h>
#include <EEPROM.h>
  U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0);  // I2C / TWI 
//***************************************************************************** 
unsigned int FullCount ; // Total size of the water tank (stored in EEPROM 02/03)
unsigned int CurrentCount ; // Current amount of water used (stored in EEPROM 00/01)
unsigned int DisplayWater ; // this is the data to send to the OLED display
float CalcWater ;      // this is the data that calculates DisplayWater (Floating type)
byte  FlagReadMemory = 0;   // flag to show that EEPROM memory has already been read
byte  value = 0;             // The return value of an EEPROM read

unsigned int SaveToEEPROM = 0; // long int to save to EEPROM
byte  one = (SaveToEEPROM & 0xFF);  // the high byte to save to EEPROM
byte  two = ((SaveToEEPROM >> 8) & 0xFF); // the low byte to save to EEPROM
byte  address;                // this is the address in EEPROM to save too

byte ScreenX = 0;   // Screen saver routine
byte ScreenY = 0;   // Screen saver routine

unsigned long PreMill = millis(); //used in time data is displayed
unsigned long CurMill = millis(); //used in time data is displayed
const long interval = 20000; // Time before Display goes blank (say about 30 sec)

const int MicTnkMt = 4;     // Microswitch "Tank Empty" on D4
const int MicTnkFl = 5;     // Microswitch "Tank Full" on D5
const int DisplayOn = 6;    // Microswitch "Display On" on D6

const int interrupt1Pin = 3;    // Interrupt 1 on pin D3 (Volts Dropping save to EEPROM)
const int interrupt0Pin = 2;    // Interrupt 0 on pin D2 (Flow Valve pulse incrament count)

//*****************************************************************************
void setup(){
// Serial.begin(9600);
  pinMode(DisplayOn, INPUT);   // declare microswitch "Display On" as an input
  pinMode(MicTnkFl, INPUT);   // declare microswitch "Tank Full" as an input
  pinMode(MicTnkMt, INPUT);   // declare microswitch "Tank Empty" as an input
  pinMode(DisplayOn, INPUT_PULLUP); // enable pullup
  pinMode(MicTnkFl, INPUT_PULLUP); // enable pullup
  pinMode(MicTnkMt, INPUT_PULLUP); // enable pullup
  attachInterrupt(digitalPinToInterrupt(2), InteruptFlowValve, FALLING);// interrupt 0
  pinMode(interrupt1Pin, INPUT_PULLUP); // remove this when full circuit is connected
  attachInterrupt(digitalPinToInterrupt(3), InteruptSaveEEPROM, FALLING);// interrupt 1


  pinMode(LED_BUILTIN, OUTPUT);//check power fail interrupt routine LED13
}
//*****************************************************************************
void loop(void) {
// It's necessary to convert unsigned int to float values to do multiplication & division
  CalcWater =(((float)FullCount-(float)CurrentCount)/(float)FullCount)*100;
  DisplayWater =(unsigned int) CalcWater ;
  
  CurMill = millis();
  if (CurMill - PreMill <= interval) // check if it's time to turn off the display
  { Display(); }

  else 
    {ScreenSaver();}    // display either ScreenSaver or ClearDisplay
//  { ClearDisplay(); } // display either ScreenSaver or ClearDisplay

  if (FlagReadMemory == 0){ // check to see if its necessary to read eeprom
  ReadEEPROM(); }          // yes it is so go and read the EEPROM

if (digitalRead(MicTnkFl) == LOW) // When "Tank Full" button is pressed zero the count
  { TankFull(); }

if (digitalRead(MicTnkMt) == LOW) // When "Tank Empty"button pressed save count to EEPROM
  { TankEmpty(); }

if (digitalRead(DisplayOn) == LOW) // When "Display On"button pressed turn display On
  { PreMill = millis();  } // to allow display to show new value
//   Serial.println(CurrentCount);
  }
//*****************************************************************************
void EEPROMSave(){ // subroutine to save a long int to EEPROM
// Entry req ("address"= address of location) ("SaveToEEPROM"= long int to save)
 
byte one = (SaveToEEPROM & 0xFF);  // Low byte to save "& to remove High Byte"
byte two = ((SaveToEEPROM >> 8) & 0xFF); // Rotate right  High byte to save
 
  EEPROM.update(address, two);     // Save High Byte (only if it's different)
  EEPROM.update(address +1, one);  // Save Low Byte  (only if it's different)
  }
//*****************************************************************************
void TankFull() {  // When "Tank Full" button is pressed zero the current count
  delay(50); // Wait for 50 ms to debounce the key
while (digitalRead(MicTnkFl) == LOW) {
}  // Do nothing whilst key is pressed
  delay(50); // Wait for 50 ms to debounce the key 
  CurrentCount = 0; //
//  Serial.println ("Current Count Zeroed ");
  PreMill = millis(); // to allow display to show new value
}
//*****************************************************************************
void TankEmpty() {  // When "Tank Empty' save "CurrentCount" to EEPROM at "FullCount 02/03"
// this routine only needs to be run once to calibrate flow valve
  delay(50); // Wait for 50 ms to debounce the key
while (digitalRead(MicTnkMt) == LOW) {
}  // Do nothing whilst key is pressed
  delay(50); // Wait for 50 ms to debounce the key 

//*************Save Routine here !!!!!!!!!!!!!!
  address = 0x02;               // set the address to save at 02/03 "FullCount"
  SaveToEEPROM = CurrentCount;  // save "CurrentCount" to "FullCount"
  EEPROMSave();
  FullCount = CurrentCount;   // also make certain that FullCount is equal to CurrentCount
//*************Save Routine here !!!!!!!!!!!!!!
//  Serial.println ("Tank is Empty - Save Current count to EEPROM ");
  PreMill = millis(); // to allow display to show new value
}
//*****************************************************************************
void InteruptSaveEEPROM(){ // When power fails, save "CurrentCount" to EEPROM 00/01
  if (FlagReadMemory == 1){ // save only when EEprom memory has been read at startup
   detachInterrupt(digitalPinToInterrupt(3)); //disable interrupt 1
 //*************Save Routine below !!!!!!!!!!!!!!
  address = 0x00;               // set the address to save at 00/01 "CurrentCount"
  SaveToEEPROM = CurrentCount;  // data to save
  EEPROMSave();
//*************Save Routine above !!!!!!!!!!!!!!
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH 
//  Serial.println ("Interrupt Low Volts occured");
  attachInterrupt(digitalPinToInterrupt(3), InteruptSaveEEPROM, FALLING);// re-enable
    }
  }
//*****************************************************************************
void ReadEEPROM() // read the EEPROM when unit first powers up
// and save to "CurrentCount" and "FullCount"
// This only happens once when power is turned on- flag is set to prevent further read's
{
  value = EEPROM.read(0);
  CurrentCount = (byte) value;        //
  CurrentCount = CurrentCount<<8;     // Rotate to the left
  value = EEPROM.read(1);
  CurrentCount |= value;        //logical OR keeps x_high intact in combined

  value = EEPROM.read(2);
  FullCount = (byte) value;        //
  FullCount = FullCount<<8;     // Rotate to the left
  value = EEPROM.read(3);
  FullCount |= value;        //logical OR keeps x_high intact in combined
  
  FlagReadMemory = 1;    // now turn this routine OFF - only required at startup
}
//*****************************************************************************
void Display() // Display loop
{
  u8g.firstPage();  
  do {
  u8g.setFont(u8g_font_fub49n); // Large Bold font
  u8g.setPrintPos(5,57);
  u8g.print (DisplayWater);
  u8g.setFont(u8g_font_fur30r); // smaller font
  if (DisplayWater != 100){     // don't display "%" if it's 100% (not enough room)
  u8g.drawStr( 87, 50, "%");
  }
 } while( u8g.nextPage() );
}
//*****************************************************************************
void ScreenSaver(){ // Draws 1 pixel at a time across screen 1 line after the other
  ScreenX = ScreenX+1;
  if (ScreenX == 128){
    ScreenX = 0;
    ScreenY=ScreenY+1;
    if (ScreenY == 64){
      ScreenY = 0;
      }}
  u8g.firstPage();
  do {
    u8g.drawPixel(ScreenX,ScreenY);
  } while( u8g.nextPage() );
}
//*****************************************************************************
// Note: either use this routine or the ScreenSaver routine above
void ClearDisplay()  // Clears the display
{
  u8g.firstPage();
  do {
  u8g.setFont(u8g_font_fub49n); // Large Bold font
  u8g.setPrintPos(5,57);
  u8g.print ("   ");
 } while( u8g.nextPage() );
}
//*****************************************************************************
void InteruptFlowValve() { // Each time the flow valve turns an interrupt is caused
  CurrentCount = CurrentCount+1;
  PreMill = millis();
//  Serial.println(CurrentCount);
}
//*****************************************************************************
