/*Overall, the code orchestrates a control cycle for a refrigeration system,
incorporating temperature monitoring, cooling, defrosting, and various indicators for system status.
The LCD provides real-time information, and the code includes mechanisms to handle potential issues like 
cooling failures.
Further analytic explanation can be found in the doc file within the same folder*/

//===============================================================================
//===============================================================================
//===============================================================================

/*Before real operation adjust the following;

1- pins
2- const unsigned long defrostInterval = 600000;  // Defrost interval in milliseconds (60 seconds)
3- const int defrostDuration = 3000; // defrost duration
4- const int secondsThreshold = 10; // threshold time elapseed for monitoring temperature
5- const int monitorTempThreshold = -10;  // Added threshold for monitoring temperature
6- const unsigned long blinkInterval = 500;// Define the blinking interval for pin 41 in milliseconds
7- const unsigned long resetInterval = 20*24*60*60*1000; // Define the interval for global automatic reset in milliseconds (2 minutes)
8- if (freezerTemperatureAboveThreshold(5, 10000)); // time & seconds
9- scrollMessage(1, scrollingMessage, 600, totalColumns); // Scroll speed the predefined message on the LCD
10-delays()
11-units of time
12-for (int countdown = 300; countdown > 0; countdown--) // startup count down
*/

#include <DallasTemperature.h>
#include <LiquidCrystal.h>
#include <TimerOne.h>

// Define one-wire communication for temperature sensors
OneWire oneWire1(52);  // room temperature sensor
DallasTemperature sensors1(&oneWire1);

OneWire oneWire2(24);  // fridge temperature sensor
DallasTemperature sensors2(&oneWire2);

OneWire oneWire3(22);  // freezer temperature sensor
DallasTemperature sensors3(&oneWire3);

// Define relay pins and LCD pins
const int compressorRelay = 25;
const int defrostRelay = 23;
const int coolfailRelay = 41;

const int pin_RS = 8;
const int pin_EN = 9;
const int pin_d4 = 4;
const int pin_d5 = 5;
const int pin_d6 = 6;
const int pin_d7 = 7;
const int pin_BL = 10;
LiquidCrystal lcd(pin_RS, pin_EN, pin_d4, pin_d5, pin_d6, pin_d7);

// Define LCD display parameters
int totalColumns = 16;
int totalRows = 2;

// Define timing variables
unsigned long startTime = 0;
unsigned long lastDefrostTime = 0;
const unsigned long defrostInterval = 10000;  // Defrost interval in milliseconds (60 seconds)
const int defrostDuration = 30000;
const int secondsThreshold = 10;
const int monitorTempThreshold = -10;  // Added threshold for monitoring temperature

// Define the states of the control cycle
enum CycleState
{
  COOLING,
  DEFROST,
  MONITOR_TEMP,
};

CycleState currentCycleState = COOLING;
unsigned long cycleStartTime = 0;

// Define the interval for automatic reset in milliseconds (2 minutes)
const unsigned long resetInterval = 240000;
unsigned long lastResetTime = 0;

// Global variable to track the time the freezer temperature goes above 5°C
static unsigned long aboveThresholdStartTime = 0;

// Define scrolling message for LCD
String scrollingMessage = "Three temperature sensors 10/10/2023;room, fridge, freezer & total operating time controller...";

// Define the blinking interval for pin 41 in milliseconds
const unsigned long blinkInterval = 500;

// Function to scroll a message on the LCD
void scrollMessage(int row, String message, int delayTime, int totalColumns)
{
  // Padding message with spaces for scrolling effect
  for (int i = 0; i < totalColumns; i++)
  {
    message = " " + message;
  }
  message = message + " ";

  // Scroll the message on the LCD
  for (int position = 0; position < message.length(); position++)
  {
    lcd.setCursor(0, row);
    lcd.print(message.substring(position, position + totalColumns));
    delay(delayTime);
  }
}

// Timer1 callback function for blinking (blinking independent of code)
void timer1Callback()
{
  // Check if the setup phase is completed
  if (millis() > 3000) {  // to prevent coolfailRelay pin 41 from unnecessary instantaneous toggling LOW/HIGH during startup
    // Toggle the state of the coolfail relay
    if (digitalRead(coolfailRelay) == HIGH)
    {
      digitalWrite(coolfailRelay, LOW);
    }
    else
    {
      digitalWrite(coolfailRelay, HIGH);
    }
  }
}

//===============================================================================
//===============================================================================
//================================================================================



// Setup function
void setup(void)
{
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);

  lcd.print("SETUP CONTROLLER");
  delay(1900);

lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(" Startup 4 min ");
  //delay(1000);



   // Display countdown during startup
  for (int countdown = 10; countdown > 0; countdown--)
  {
    //lcd.clear();
    lcd.setCursor(5, 1);
    lcd.print("" + String(countdown) + " sec ");
    delay(1000); //countdown speed
  }





  


  //delay(1000);

 // lcd.setCursor(0, 1);

 // lcd.print("(WAIT 4 minutes)");
  //delay(1000);

  // Set up relay pins
  pinMode(compressorRelay, OUTPUT);
  pinMode(defrostRelay, OUTPUT);
  pinMode(coolfailRelay, OUTPUT);
  digitalWrite(coolfailRelay, LOW);

  // Initialize temperature sensors
  sensors1.begin();
  sensors2.begin();
  sensors3.begin();

  // Set initial timing variables
  startTime = millis();
  lastDefrostTime = millis();
  cycleStartTime = millis();

  // Initialize Timer1 with the timer1Callback function and the desired interval
  Timer1.initialize(blinkInterval * 1000);  // Timer interval is in microseconds
  Timer1.attachInterrupt(timer1Callback);
  Timer1.stop();  // Stop the timer initially

  // Set the initial state of pin 41 to LOW
  digitalWrite(coolfailRelay, LOW);
  //delay(240000); // starting delay for compressor equalizing pressures after every power failure 
}


//===============================================================================
//===============================================================================
//================================================================================





// Main loop function
void loop(void)
{
  unsigned long currentMillis = millis();

  // Request temperature readings from sensors
  sensors1.requestTemperatures();
  sensors2.requestTemperatures();
  sensors3.requestTemperatures();
  float room = sensors1.getTempCByIndex(0);
  float fridge = sensors2.getTempCByIndex(0);
  float freezer = sensors3.getTempCByIndex(0);
   unsigned long elapsedTime = currentMillis - startTime;
   float totalRunningHours = elapsedTime / (1000.0 * 3600.0);

  // Check if it's time to reset every 2 minutes
  if (currentMillis - lastResetTime >= resetInterval)
  {
    // Perform any cleanup or actions before resetting, if needed
    delay(1000);                      // Wait for stability (optional)
    asm volatile("  jmp 0");          // Jump to the beginning of the program, resetting it
  }

  // Display a welcome message on the LCD
  lcd.clear();
  lcd.setCursor(0, 0); //column, row
  lcd.print("....GOOD DAY....");
  delay(200);
  lcd.clear();
  delay(200);
  lcd.print("....GOOD DAY....");
  delay(200);
  lcd.clear();

 // if total running hours <one hour, then print seconds, else print hours

if (totalRunningHours < 1.0 / 60.0) {
    lcd.setCursor(0, 0);
    lcd.print("Time=        sec");
    lcd.setCursor(5, 0);
    lcd.print(totalRunningHours * 3600, 2); // Convert totalRunningHours to seconds
  } else if (totalRunningHours < 1.0) {
    lcd.setCursor(0, 0);
    lcd.print("Time=        min");
    lcd.setCursor(5, 0);
    lcd.print(totalRunningHours * 60, 2); // Convert totalRunningHours to minutes
  } else {
    lcd.setCursor(0, 0);
    lcd.print("Time=         Hr");
    lcd.setCursor(5, 0);
    lcd.print(totalRunningHours, 2); // Display totalRunningHours in hours
  }


  // Scroll the predefined message on the LCD
  scrollMessage(1, scrollingMessage, 600, totalColumns);

  // Display information about time and temperature range on the LCD
  lcd.setCursor(0, 0);
  lcd.print("Time in hours    ");
  lcd.setCursor(14, 1);
  lcd.setCursor(0, 1);
  lcd.print("Temp.-55to+125 C");
  lcd.setCursor(14, 1);
  lcd.print((char)223); // Degree symbol
  delay(2000);

  // Display room and fridge temperatures on the LCD
  lcd.setCursor(0, 0);
  lcd.print("Room   =       C");
  lcd.setCursor(14, 0);
  lcd.print((char)223);
  lcd.setCursor(8, 0);
  lcd.print(room);//display room temperature

  lcd.setCursor(0, 1);
  lcd.print("Fridge =       C");
  lcd.setCursor(14, 1);
  lcd.print((char)223);
  lcd.setCursor(8, 1);
  lcd.print(fridge);//display fridge temperature

  delay(1000);

  // Repeat the temperature display for fridge and freezer
  lcd.setCursor(0, 0);
  lcd.print("Fridge =       C");
  lcd.setCursor(14, 0);
  lcd.print((char)223);
  lcd.setCursor(8, 0);
  lcd.print(fridge);

  lcd.setCursor(0, 1);
  lcd.print("Freezer=       C");
  lcd.setCursor(14, 1);
  lcd.print((char)223);
  lcd.setCursor(8, 1);
  lcd.print(freezer);//display freezer temperature

   

  // Repeat the temperature display for freezer
 lcd.clear();

   lcd.setCursor(0, 0);
  lcd.print("Freezer=       C");
  lcd.setCursor(14, 0);
  lcd.print((char)223);
  lcd.setCursor(8, 0);
  lcd.print(freezer);
 delay(1000);

if (totalRunningHours < 1.0 / 60.0) {
    lcd.setCursor(0, 1);
    lcd.print("Time=        sec");
    lcd.setCursor(5, 1);
    lcd.print(totalRunningHours * 3600, 2); // Convert totalRunningHours to seconds
  } else if (totalRunningHours < 1.0) {
    lcd.setCursor(0, 1);
    lcd.print("Time=        min");
    lcd.setCursor(5, 1);
    lcd.print(totalRunningHours * 60, 2); // Convert totalRunningHours to minutes
  } else {
    lcd.setCursor(0, 1);
    lcd.print("Time=         Hr");
    lcd.setCursor(5, 1);
    lcd.print(totalRunningHours, 2); // Display totalRunningHours in hours
  }

 
  // Update the last reset time if a reset is not triggered
  lastResetTime = currentMillis;

  // Debug statements
  Serial.println("Current Millis: " + String(currentMillis));
  Serial.println("Last Defrost Time: " + String(lastDefrostTime));

  // Check for defrost cycle every 60 seconds & the 49.7 days millis rollover prevention
  if ((currentMillis - lastDefrostTime >= defrostInterval) || (currentMillis < lastDefrostTime && (4294967295UL - lastDefrostTime + currentMillis) >= defrostInterval))
  {
   lcd.setCursor(0,0);
   lcd.clear();
   lcd.print(" DEFROST START ");
   digitalWrite(coolfailRelay, LOW);
digitalWrite(defrostRelay, HIGH);
   delay(1000);
 lcd.setCursor(0,1);
   lcd.print("DEFROSTING 20min");
   delay(100);
digitalWrite(coolfailRelay, LOW);
digitalWrite(defrostRelay, HIGH);


lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Defrosting 20min");
  //delay(1000);

digitalWrite(coolfailRelay, LOW);
digitalWrite(defrostRelay, HIGH);

   // Display countdown during startup
  for (int countdown = 30; countdown > 0; countdown--)
  {
    //lcd.clear();
    lcd.setCursor(5, 1);
    lcd.print("" + String(countdown) + " min ");
    delay(1000); //counddown speed

    digitalWrite(coolfailRelay, LOW);
digitalWrite(defrostRelay, HIGH);
  }








    startDefrostCycle();
    lcd.clear();
   
    lastDefrostTime = currentMillis;
  }

  // Perform cooling, defrost, or monitor temperature cycle actions
  if (currentCycleState == COOLING)
  {
    performCoolingCycle();
  }
  else if (currentCycleState == DEFROST)
  {
    performDefrostCycle();
  }
  else if (currentCycleState == MONITOR_TEMP)
  {
    monitorFreezerTemperature();
  }

  // Print total running hours to serial monitor
 
 

  Serial.print("Total Running Hours: ");
  Serial.println(totalRunningHours);

  // Print total running hours to LCD

if (totalRunningHours < 1.0 / 60.0) {
    lcd.setCursor(0, 1);
    lcd.print("Time=        sec");
    lcd.setCursor(5, 1);
    lcd.print(totalRunningHours * 3600, 2); // Convert totalRunningHours to seconds, decimal points
  } else if (totalRunningHours < 1.0) {
    lcd.setCursor(0, 1);
    lcd.print("Time=        min");
    lcd.setCursor(5, 1);
    lcd.print(totalRunningHours * 60, 2); // Convert totalRunningHours to minutes, decimal points
  } else {
    lcd.setCursor(0, 1);
    lcd.print("Time=         Hr");
    lcd.setCursor(5, 1);
    lcd.print(totalRunningHours, 2); // Display totalRunningHours in hours,decimal points
  }

  delay(3000);

  // Check if the freezer temperature is above (threshold, for more than interval)
  if (freezerTemperatureAboveThreshold(5, 10000))
  {
    // Start the blinking of pin 41

  lcd.clear();

   lcd.setCursor(0,0);
  
   lcd.print("     ALARM     ");
 lcd.setCursor(0,1);
   lcd.print("   COOL FAIL   ");
    
  
   // delay(1000);
    Timer1.start();
  }
  else
  {
    // Stop the blinking of pin 41
    Timer1.stop();
    // If not in the fail state, turn off the relay
    digitalWrite(coolfailRelay, LOW);
  }

  //delay(2000);
}

// Function to perform cooling cycle based on freezer temperature
void performCoolingCycle()
{
  sensors1.requestTemperatures();
  sensors2.requestTemperatures();
  sensors3.requestTemperatures();

  float freezer = sensors3.getTempCByIndex(0);
  updateFreezerRelayState(freezer);
}

// Function to update the state of the freezer relay based on temperature
void updateFreezerRelayState(float freezerTemp)
{
  if (freezerTemp >= 0 || freezerTemp >= monitorTempThreshold)
  {
    if (millis() - cycleStartTime >= secondsThreshold * 1000)
    {
      digitalWrite(compressorRelay, HIGH);

      lcd.setCursor(0, 1);
      lcd.print(" COMPRESSOR RUN ");
      delay(5000);
      lcd.setCursor(0, 1);
      lcd.print("TEMPERATURE DROP");
      delay(5000);

      cycleStartTime = millis();
    }
  }
  else
  {
    digitalWrite(compressorRelay, LOW);
    currentCycleState = MONITOR_TEMP; // Set state to monitor temperature when below the threshold
  }
}

// Function to start the defrost cycle
void startDefrostCycle()
{
  digitalWrite(compressorRelay, LOW);
  digitalWrite(defrostRelay, HIGH);
  currentCycleState = DEFROST;

  unsigned long defrostEndTime = millis() + defrostDuration;
  while (millis() < defrostEndTime)
  {
    sensors1.requestTemperatures();
    sensors2.requestTemperatures();
    sensors3.requestTemperatures();
    delay(100);
  }

  digitalWrite(defrostRelay, LOW);
  currentCycleState = MONITOR_TEMP;
}

// Function to perform actions during the defrost cycle
void performDefrostCycle()
{
  // Perform actions during defrost cycle, if needed
}

// Function to monitor freezer temperature and transition to cooling state
void monitorFreezerTemperature()
{
  sensors1.requestTemperatures();
  sensors2.requestTemperatures();
  sensors3.requestTemperatures();

  float freezer = sensors3.getTempCByIndex(0);

  if (freezer >= 0 && (millis() - cycleStartTime >= secondsThreshold * 1000))
  {
    digitalWrite(compressorRelay, LOW);
    currentCycleState = COOLING;
    cycleStartTime = millis(); // Update cycle start time
  }
}

// Function to check if freezer temperature is above a threshold for a certain duration
bool freezerTemperatureAboveThreshold(float threshold, unsigned long duration)
{
  sensors1.requestTemperatures();
  sensors2.requestTemperatures();
  sensors3.requestTemperatures();

  float freezer = sensors3.getTempCByIndex(0);

  if (freezer >= threshold)
  {
    if (millis() - aboveThresholdStartTime >= duration)
    {
      aboveThresholdStartTime = millis();
      return true;
    }
  }
  else
  {
    aboveThresholdStartTime = millis();
  }

  return false;
}



freezer
fridge
room
cooling
(-10°C~0°C)
defrosting
(3sec/1min )
alarm
(>5°C)
(-55°C to +125°C)