/*
  Copyright (C) 2024, Pablo César Galdo Regueiro.
  info.wokwi(at)pcgaldo.com

  Project in editing process.
  Does not work on real hardware.
  Problems with reboots on startup and freezes.

  v1 works in real hardware:
  https://wokwi.com/projects/391359771446388737

  Press "Ctrl" to to lock a button press.

  License

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <https://www.gnu.org/licenses/>
*/

// Include Libraries
#include "Wire.h"
#include <U8g2lib.h>
#include "RTClib.h"
#include "EEPROM.h"
#include "Bounce2.h"

// Splash Logo
//#include "splash.h"

// Display Settings
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// OLED Display Instance
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// Single value to check data validity
#define MAGIC_KEY 0x42

// Address in EEPROM where the magic key is to be stored
#define MAGIC_KEY_ADDR 0

// Variable to store the start time of the delay
unsigned long displayStartTime = 0;

// Delay time in milliseconds
const unsigned long displayDelay = 100;

// Variable to indicate if the delay is complete
bool displayUpdateComplete = false;

// Buttons input pins
#define IN_MENU 3
#define IN_DOWN 4
#define IN_UP 5

// Digital inputs debouncing
int dbintervalmenu = 150;        // Pressing menu button time
int dbintervalupdown = 150;      // Pressing menu button time
Bounce DB_MENU = Bounce();
Bounce DB_UP = Bounce();
Bounce DB_DOWN = Bounce();

// Debounced inputs
bool DB_IN_MENU = 0;
bool DB_IN_UP = 0;
bool DB_IN_DOWN = 0;

// Buttons Delay Settings
const int buttonsDelay = 500;

// Last Debounce Times for Buttons
unsigned long lastDelayTime = 0;

// Relay Output Pin
#define OUT_RELAY 2

// Real-Time Clock (RTC) Instance
RTC_DS1307 rtc;

// Temperature hysteresis
float t_hyst = 0.5;

// Thermistor Parameters
const float BETA = 3950;

// Setpoints
float t_sp;       // Working setpoint
float t_sp1 = 17; // Setpoint 1
float t_sp2 = 20; // Setpoint 2

// Temperature
float t_pv;  // Sensor temperature

// Relay State
bool relayState = true;

// Enumeration for Different Menu States
enum MenuState {
  MAIN,
  SETPOINT,
  TIME_STATE,
  DATETIME
};

MenuState menuState = MAIN;

// Enumeration for Different Setpoint States
enum SetpointState {
  SP1,
  SP2
};

SetpointState setpointState = SP1;

// Enumeration for Editing Setpoints
enum EditingSetpoint {
  EDIT_SP1,
  EDIT_SP2
};

EditingSetpoint editingSetpoint = EDIT_SP1; // Initially editing Setpoint 1

// Variable to track the current hour state for time-dependent settings
int dateTimeState = 0;

// Enumeration for Different Date and Time Fields
enum DateTimeField {
  DAY,
  MONTH,
  YEAR,
  HOUR,
  MINUTE
};

DateTimeField WatchState = DAY;

// Enumeration for Days of the Week
enum Weekday {
  SUNDAY,
  MONDAY,
  TUESDAY,
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY
};

// Variables for blinking colons
unsigned long currentColonBlinkMillis = 0;
unsigned long lastColonBlinkMillis = 0;
bool colonVisible = true;

// Variable for blinking relay status
unsigned long currentRelayBlinkMillis = 0;
unsigned long lastRelayBlinkMillis = 0;
bool relayVisible = true;

// Flag to Track Button Presses
bool buttonPressed = false;

// Array to Store Time States
bool schedule[7][24];

int currentDay;
int currentHour;

int navDay = 1;
int navHour = 0;

// Current Date and Time
DateTime now;

// Variables to track the time since the last interaction and the display state
unsigned long lastInteractionTime = 0;
bool displayOn = true;

// ========== Initialization Functions ==========

void setup() {
  Serial.begin(9600);

  // Initialize the real-time clock (RTC)
  if (!initializeRTC()) {
    Serial.println(F("Couldn't find RTC"));
    Serial.flush();
    abort();
  }

  // Inicializa el display
  u8g2.begin();

  // Set the output mode for the relay pin
  pinMode(OUT_RELAY, OUTPUT);
  digitalWrite(OUT_RELAY, LOW);

  // Set the input mode for the buttons pins
  pinMode(IN_MENU, INPUT);
  DB_MENU.attach(IN_MENU);
  DB_MENU.interval(dbintervalmenu);

  pinMode(IN_UP, INPUT);
  DB_UP.attach(IN_UP);
  DB_UP.interval(dbintervalupdown);

  pinMode(IN_DOWN, INPUT);
  DB_DOWN.attach(IN_DOWN);
  DB_DOWN.interval(dbintervalupdown);

  // Initialize EEPROM values
  initializeEEPROM();

  // Initialize navigation variables
  navDay = 1;
  navHour = 0;

  // Display a splash screen for 2 seconds
  displaySplashScreen();
}

bool initializeRTC() {
  if (!rtc.begin()) {
    Serial.println(F("Error: Couldn't find RTC. Check connections!"));
    return false;
  }
  return true;
}

void displaySplashScreen() {
  u8g2.clearBuffer();  // Limpia el búfer de pantalla
  //u8g2.drawBitmap(0, 0, 128 / 8, 64, splash);  // Dibuja el bitmap
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Establece la fuente para el texto
  u8g2.setCursor(0, 46);  // Posiciona el cursor
  u8g2.print(F("  CHRONO-THERMOSTAT"));  // Escribe el texto
  u8g2.setCursor(0, 56);  // Posiciona el cursor para el siguiente texto
  u8g2.print(F("(c) Pablo Galdo, 2024"));  // Escribe el siguiente texto
  u8g2.sendBuffer();  // Muestra el contenido del búfer en la pantalla
  delay(2000);  // Espera 2 segundos
  u8g2.clearBuffer();  // Limpia la pantalla
}

// ========== Main Program Loop ==========

void loop() {

  // Update debounce instances
  DB_MENU.update();
  DB_UP.update();
  DB_DOWN.update();

  // Read debounced values
  DB_IN_MENU = DB_MENU.read();
  DB_IN_UP = DB_UP.read();
  DB_IN_DOWN = DB_DOWN.read();
  
  // Get the current time
  unsigned long currentTime = millis();

  // Check for inactivity for 1 minute
  if ((currentTime - lastInteractionTime) > 60000 && displayOn) {
    turnOffDisplay();  // Turn off the display if 1 minute has passed and it's on
  }

  // Get the current date and time from the RTC
  now = rtc.now();
  //currentDay = (now.dayOfTheWeek() + 6) % 7; // Set to start on Monday
  currentDay = now.dayOfTheWeek();
  currentHour = now.hour();

  // Read analog sensor value and calculate temperature
  int analogValue = analogRead(A0);
  t_pv = calculateTemperature(analogValue);

  // Update the setpoint (t_sp) based on the current state
  if (schedule[currentDay][currentHour]) {
    t_sp = t_sp2;
  } else {
    t_sp = t_sp1;
  }

  // Control the relay based on temperature and hysteresis
  controlRelay(t_pv);

  // Display current information on the OLED screen
  updateDisplay();

  // Execute actions based on the current menu state
  executeMenuActions();

  // Check if any button is pressed
  if (DB_IN_MENU == 1 || DB_IN_DOWN == 1 || DB_IN_UP == 1) {
    if (!displayOn) {
      buttonPressed = true;
      turnOnDisplay();  // Turn on the display if it was off
    }
    lastInteractionTime = currentTime;  // Update the time of the last interaction
  }
}

// ========== Program Functions ==========

// Function to turn off the display
void turnOffDisplay() {
  u8g2.setPowerSave(true);
  displayOn = false;
}

// Function to turn on the display
void turnOnDisplay() {
  u8g2.setPowerSave(false);
  displayOn = true;
}

// Function to calculate temperature from analog sensor value
float calculateTemperature(int analogValue) {
  return 1 / (log(1 / (1023. / analogValue - 1)) / BETA + 1.0 / 298.15) - 273.15;
}

// Function to control the relay based on temperature and hysteresis
void controlRelay(float t_pv) {
  if (t_pv <= t_sp - t_hyst) {
    relayState = true;
    digitalWrite(OUT_RELAY, HIGH);
  } else if (t_pv >= t_sp + t_hyst) {
    relayState = false;
    digitalWrite(OUT_RELAY, LOW);
  }
}

// Function to update the OLED display
void updateDisplay() {
  // Si el retraso no ha comenzado, iniciarlo
  if (!displayUpdateComplete) {
    if (millis() - displayStartTime >= displayDelay) {
      u8g2.sendBuffer();  // Actualiza la pantalla con el contenido del búfer
      u8g2.clearBuffer(); // Limpia el búfer de pantalla
      displayUpdateComplete = true;
    }
  } else {
    displayStartTime = millis();
    displayUpdateComplete = false;
  }
}

// Handle main menu interactions.
void handleMainMenu() {
  editingSetpoint = EDIT_SP1;
  dateTimeState = 0;
  WatchState = DAY;

  // Check if menu button is pressed
  if (displayOn == true && DB_IN_MENU == 1 && DB_IN_DOWN == 0 && DB_IN_UP == 0) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        lastDelayTime = millis();
        menuState = SETPOINT;
        buttonPressed = true;
      }
    }
  } else if (displayOn == true && DB_IN_MENU == 1 && DB_IN_DOWN == 1 && DB_IN_UP == 0) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        lastDelayTime = millis();
        menuState = TIME_STATE;
        buttonPressed = true;
      }
    }
  } else if (displayOn == true && DB_IN_MENU == 1 && DB_IN_DOWN == 0 && DB_IN_UP == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        lastDelayTime = millis();
        menuState = DATETIME;
        buttonPressed = true;
      }
    }
  } else {
    buttonPressed = false;
  }
}

// Handle setpoint menu interactions.
void handleSetpointMenu() {
  if (displayOn == false) {
    menuState = MAIN; 
  }
  if (DB_IN_MENU == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        if (editingSetpoint == EDIT_SP1) {
          editingSetpoint = EDIT_SP2;
        } else {
          saveSetpointsToEEPROM();
          editingSetpoint = EDIT_SP2;
          menuState = MAIN;
        }
        lastDelayTime = millis();
        buttonPressed = true;
      }
    }
  } else {
    buttonPressed = false;
  }

  if (DB_IN_DOWN == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      switch (editingSetpoint) {
        case EDIT_SP1:
          t_sp1 = constrain(t_sp1 - 0.5, 15.0, min(t_sp2 - 0.5, 30.0));
          break;
        case EDIT_SP2:
          t_sp2 = constrain(t_sp2 - 0.5, max(t_sp1 + 0.5, 15.0), 30.0);
          break;
      }
      lastDelayTime = millis();
    }
  }

  if (DB_IN_UP == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      switch (editingSetpoint) {
        case EDIT_SP1:
          t_sp1 = constrain(t_sp1 + 0.5, 15.0, min(t_sp2 - 0.5, 30.0));
          break;
        case EDIT_SP2:
          t_sp2 = constrain(t_sp2 + 0.5, max(t_sp1 + 0.5, 15.0), 30.0);
          break;
      }
      lastDelayTime = millis();
    }
  }
}

// Handle time state menu interactions.
void handleTimeStateMenu() {
  if (displayOn == false) {
    menuState = MAIN;
  }

 // Check if both up and down buttons are pressed
  if (DB_IN_UP == 1 && DB_IN_DOWN == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        lastDelayTime = millis();
        menuState = MAIN;
        navDay = 1;
        navHour = 0;
        buttonPressed = true;
      }
    }
  } else {
    buttonPressed = false;
  }

  if (DB_IN_MENU == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        schedule[navDay][navHour] = !schedule[navDay][navHour];
        saveScheduleToEEPROM();
        lastDelayTime = millis();
      }
    }
  }

  if (DB_IN_UP == 1 && DB_IN_DOWN == 0) {
    if (millis() - lastDelayTime > buttonsDelay) {
      navHour++;
      if (navHour > 23) navHour = 0;
      lastDelayTime = millis();
    }
  }

  if (DB_IN_DOWN == 1 && DB_IN_UP == 0) {
    if (millis() - lastDelayTime > buttonsDelay) {
      navDay++;
      if (navDay > 6) navDay = 0;
      lastDelayTime = millis();
    }
  }
}

// Handle date and time adjustment menu interactions.
void handleDateTimeMenu() {
  if (displayOn == false) {
    WatchState = DAY;
    menuState = MAIN;
  }

  if (DB_IN_MENU == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        switch (WatchState) {
          case DAY:
            WatchState = MONTH;
            break;
          case MONTH:
            WatchState = YEAR;
            break;
          case YEAR:
            WatchState = HOUR;
            break;
          case HOUR:
            WatchState = MINUTE;
            break;
          case MINUTE:
            menuState = MAIN;
            break;
        }
        lastDelayTime = millis();
        buttonPressed = true;
      }
    }
  } else {
    buttonPressed = false;
  }

  if (DB_IN_DOWN == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      decrementDateTimeField();
      lastDelayTime = millis();
    }
  }

  if (DB_IN_UP == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      incrementDateTimeField();
      lastDelayTime = millis();
    }
  }
}

void incrementDateTimeField() {
  DateTime now = rtc.now();
  switch (WatchState) {
    case DAY:
      rtc.adjust(DateTime(now.year(), now.month(), (now.day() % 31) + 1, now.hour(), now.minute(), now.second()));
      break;
    case MONTH:
      rtc.adjust(DateTime(now.year(), (now.month() % 12) + 1, now.day(), now.hour(), now.minute(), now.second()));
      break;
    case YEAR:
      rtc.adjust(DateTime((now.year() % 2099) + 1, now.month(), now.day(), now.hour(), now.minute(), now.second()));
      break;
    case HOUR:
      rtc.adjust(DateTime(now.year(), now.month(), now.day(), (now.hour() % 23) + 1, now.minute(), now.second()));
      break;
    case MINUTE:
      rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), (now.minute() % 59) + 1, now.second()));
      break;
  }
}

void decrementDateTimeField() {
  DateTime now = rtc.now();
  switch (WatchState) {
    case DAY:
      rtc.adjust(DateTime(now.year(), now.month(), (now.day() == 1) ? 31 : now.day() - 1, now.hour(), now.minute(), now.second()));
      break;
    case MONTH:
      rtc.adjust(DateTime(now.year(), (now.month() == 1) ? 12 : now.month() - 1, now.day(), now.hour(), now.minute(), now.second()));
      break;
    case YEAR:
      rtc.adjust(DateTime((now.year() == 2000) ? 2099 : now.year() - 1, now.month(), now.day(), now.hour(), now.minute(), now.second()));
      break;
    case HOUR:
      rtc.adjust(DateTime(now.year(), now.month(), now.day(), (now.hour() == 0) ? 23 : now.hour() - 1, now.minute(), now.second()));
      break;
    case MINUTE:
      rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), (now.minute() == 0) ? 59 : now.minute() - 1, now.second()));
      break;
  }
}

// Display the main screen on the OLED.
void displayMainScreen() {
  u8g2.clearBuffer();  // Limpia el búfer de pantalla

  u8g2.setFont(u8g2_font_ncenB08_tr);  // Selecciona la fuente (equivalente al tamaño 1)
  u8g2.setCursor(2, 12);  // Establece la posición del cursor para la fecha
  
  // Imprimir la fecha
  if (now.day() < 10) {
    u8g2.print(F("0"));
  }
  u8g2.print(now.day(), DEC);
  u8g2.print(F("/"));
  if (now.month() < 10) {
    u8g2.print(F("0"));
  }
  u8g2.print(now.month(), DEC);
  u8g2.print(F("/"));
  u8g2.print(now.year(), DEC);

  // Establecer posición para la hora
  u8g2.setCursor(2, 24);  // Ajustar la posición para la hora
  u8g2.setFont(u8g2_font_ncenB18_tr);  // Fuente para la hora grande

  currentColonBlinkMillis = millis();

  if (currentColonBlinkMillis - lastColonBlinkMillis >= 500) {
    colonVisible = !colonVisible;
    lastColonBlinkMillis = currentColonBlinkMillis;
  }

  if (now.hour() < 10) {
    u8g2.print(F("0"));
  }
  u8g2.print(now.hour(), DEC);

  if (colonVisible) {
    u8g2.print(F(":"));
  } else {
    u8g2.print(F(" "));
  }

  if (now.minute() < 10) {
    u8g2.print(F("0"));
  }
  u8g2.print(now.minute(), DEC);

  // Mostrar el día de la semana
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Establece la fuente de tamaño 1
  u8g2.setCursor(2, 40);  // Ajustar la posición para el día de la semana

  switch (now.dayOfTheWeek()) {
    case SUNDAY:
      u8g2.print(F("SUNDAY"));
      break;
    case MONDAY:
      u8g2.print(F("MONDAY"));
      break;
    case TUESDAY:
      u8g2.print(F("TUESDAY"));
      break;
    case WEDNESDAY:
      u8g2.print(F("WEDNESDAY"));
      break;
    case THURSDAY:
      u8g2.print(F("THURSDAY"));
      break;
    case FRIDAY:
      u8g2.print(F("FRIDAY"));
      break;
    case SATURDAY:
      u8g2.print(F("SATURDAY"));
      break;
  }

  // Mostrar la temperatura
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente de tamaño 1
  u8g2.setCursor(74, 12);  // Ajustar la posición para la temperatura
  u8g2.print(F("TEMP."));
  
  u8g2.setFont(u8g2_font_ncenB18_tr);  // Fuente grande para la temperatura
  u8g2.setCursor(74, 24);
  u8g2.print(t_pv, 1);  // Mostrar la temperatura
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente pequeña para las unidades
  u8g2.print(F("C"));

  // Mostrar la temperatura objetivo (setpoint)
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente de tamaño 1
  u8g2.setCursor(74, 40);  // Ajustar la posición para la temperatura objetivo
  u8g2.print(F("SP:"));
  u8g2.print(t_sp, 1);
  u8g2.print(F("C"));

  currentRelayBlinkMillis = millis();

  if (currentRelayBlinkMillis - lastRelayBlinkMillis >= 250) {
    relayVisible = !relayVisible;
    lastRelayBlinkMillis = currentRelayBlinkMillis;
  }

  // Mostrar el estado del relé
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente de tamaño 1
  u8g2.setCursor(34, 52);  // Posición para mostrar el estado del relé
  if (relayState) {
    if (relayVisible) {
      u8g2.print(F("HEATER  ON"));
    }
  } else {
    u8g2.print(F("HEATER OFF"));
  }

  u8g2.sendBuffer();  // Actualiza la pantalla con el contenido del búfer
}

// Display the setpoint menu on the OLED.
void displaySetpointScreen() {

  u8g2.clearBuffer();  // Limpiar el búfer de la pantalla

  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente pequeña para los textos
  u8g2.setCursor(0, 12);  // Posición inicial para el texto
  u8g2.print(F("TEMPERATURE  SETPOINT"));

  // Mostrar el setpoint bajo (SP LOW)
  u8g2.setCursor(0, 32);  // Posición para "SP LOW"
  u8g2.print(F("SP LOW"));
  if (editingSetpoint == EDIT_SP1) {
    u8g2.print(F(" > "));
  } else {
    u8g2.print(F("   "));
  }
  u8g2.setFont(u8g2_font_ncenB18_tr);  // Fuente grande para mostrar el valor
  u8g2.setCursor(0, 48);  // Posición para el valor de t_sp1
  u8g2.print(t_sp1, 1);  // Mostrar el setpoint 1
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente pequeña para la "C"
  u8g2.print(F("C"));

  // Mostrar el setpoint alto (SP HI)
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente pequeña
  u8g2.setCursor(0, 64);  // Posición para "SP HI"
  u8g2.print(F("SP HI "));
  if (editingSetpoint == EDIT_SP2) {
    u8g2.print(F(" > "));
  } else {
    u8g2.print(F("   "));
  }
  u8g2.setFont(u8g2_font_ncenB18_tr);  // Fuente grande para el valor
  u8g2.setCursor(0, 80);  // Posición para el valor de t_sp2
  u8g2.print(t_sp2, 1);  // Mostrar el setpoint 2
  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente pequeña para la "C"
  u8g2.print(F("C"));

  u8g2.sendBuffer();  // Actualiza la pantalla con el contenido del búfer
}

// Display the states and their status on the screen.
// Display the time state screen on the OLED.
void displayTimeStateScreen() {

  u8g2.clearBuffer();  // Limpiar el búfer de la pantalla

  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente pequeña para los textos
  u8g2.setCursor(12, 12);  // Posición para el título "SCHEDULER"
  u8g2.println(F("SCHEDULER"));

  u8g2.setFont(u8g2_font_ncenB18_tr);  // Fuente grande para el día de la semana
  u8g2.setCursor(12, 36);  // Posición para el nombre del día
  switch (navDay) {
    case 0:
      u8g2.print(F("SUNDAY"));
      break;
    case 1:
      u8g2.print(F("MONDAY"));
      break;
    case 2:
      u8g2.print(F("TUESDAY"));
      break;
    case 3:
      u8g2.print(F("WEDNESDAY"));
      break;
    case 4:
      u8g2.print(F("THURSDAY"));
      break;
    case 5:
      u8g2.print(F("FRIDAY"));
      break;
    case 6:
      u8g2.print(F("SATURDAY"));
      break;
  }

  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente pequeña para las horas y SP
  u8g2.setCursor(16, 56);  // Posición para "HORA:"
  u8g2.println(F("HORA:"));

  u8g2.setFont(u8g2_font_ncenB18_tr);  // Fuente grande para la hora
  u8g2.setCursor(16, 72);  // Posición para navHour
  u8g2.print(navHour);

  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente pequeña para "SP:"
  u8g2.setCursor(80, 56);  // Posición para "SP:"
  u8g2.println(F("SP:"));

  u8g2.setFont(u8g2_font_ncenB18_tr);  // Fuente grande para el setpoint
  u8g2.setCursor(80, 72);  // Posición para el valor de schedule
  u8g2.print(schedule[navDay][navHour] ? F("HI") : F("LOW"));

  u8g2.sendBuffer();  // Actualiza la pantalla con el contenido del búfer
}

// Display date and time adjustment screen on the OLED.
void displayDateTimeScreen() {
  DateTime now = rtc.now();

  u8g2.clearBuffer();  // Limpiar el búfer de la pantalla

  u8g2.setFont(u8g2_font_ncenB08_tr);  // Fuente pequeña para el título
  u8g2.setCursor(0, 12);  // Posición para "DATE & TIME ADJUST."
  u8g2.println(F("DATE & TIME ADJUST."));

  u8g2.setFont(u8g2_font_ncenB18_tr);  // Fuente grande para la fecha
  u8g2.setCursor(0, 36);  // Posición para la fecha

  if (WatchState == DAY) {
    u8g2.setDrawColor(0);  // Fondo oscuro para el día
  }
  if (now.day() < 10) {
    u8g2.print(F("0"));
  }
  u8g2.print(now.day(), DEC);
  u8g2.setDrawColor(1);  // Vuelve al color blanco
  u8g2.print(F("/"));

  if (WatchState == MONTH) {
    u8g2.setDrawColor(0);  // Fondo oscuro para el mes
  }
  if (now.month() < 10) {
    u8g2.print(F("0"));
  }
  u8g2.print(now.month(), DEC);
  u8g2.setDrawColor(1);  // Vuelve al color blanco
  u8g2.print(F("/"));

  if (WatchState == YEAR) {
    u8g2.setDrawColor(0);  // Fondo oscuro para el año
  }
  u8g2.print(now.year(), DEC);
  u8g2.setDrawColor(1);  // Vuelve al color blanco
  u8g2.println();

  u8g2.setFont(u8g2_font_ncenB18_tr);  // Fuente grande para la hora
  u8g2.setCursor(0, 72);  // Posición para la hora

  if (WatchState == HOUR) {
    u8g2.setDrawColor(0);  // Fondo oscuro para la hora
  }
  if (now.hour() < 10) {
    u8g2.print(F("0"));
  }
  u8g2.print(now.hour(), DEC);
  u8g2.setDrawColor(1);  // Vuelve al color blanco
  u8g2.print(F(":"));

  if (WatchState == MINUTE) {
    u8g2.setDrawColor(0);  // Fondo oscuro para el minuto
  }
  if (now.minute() < 10) {
    u8g2.print(F("0"));
  }
  u8g2.print(now.minute(), DEC);
  u8g2.setDrawColor(1);  // Vuelve al color blanco

  u8g2.sendBuffer();  // Actualiza la pantalla con el contenido del búfer
}

void executeMenuActions() {
  switch (menuState) {
    case MAIN:
      displayMainScreen();
      handleMainMenu();
      break;
    case SETPOINT:
      displaySetpointScreen();
      handleSetpointMenu();
      break;
    case TIME_STATE:
      displayTimeStateScreen();
      handleTimeStateMenu();
      break;
    case DATETIME:
      displayDateTimeScreen();
      handleDateTimeMenu();
      break;
  }
}

void saveScheduleToEEPROM() {
  int addr = 1; // Start after the magic key
  for (int day = 0; day <= 6; day++) {
    for (int hour = 0; hour < 24; hour++) {
      EEPROM.write(addr++, schedule[day][hour]);
    }
  }
}

void saveSetpointsToEEPROM() {
  int addr = 1 + 7 * 24; // Start after the schedule array
  EEPROM.put(addr, t_sp1);
  addr += sizeof(float);
  EEPROM.put(addr, t_sp2);
}

void loadScheduleFromEEPROM() {
  int addr = 1; // Start after the magic key
  for (int day = 0; day <= 6; day++) {
    for (int hour = 0; hour < 24; hour++) {
      schedule[day][hour] = EEPROM.read(addr++);
    }
  }
}

void loadSetpointsFromEEPROM() {
  int addr = 1 + 7 * 24; // Start after the schedule array
  EEPROM.get(addr, t_sp1);
  addr += sizeof(float);
  EEPROM.get(addr, t_sp2);
}

void initializeSchedule() {
    for (int day = 0; day <= 6; day++) {
        for (int hour = 0; hour < 24; hour++) {
            schedule[day][hour] = false;
        }
    }
} 

void initializeEEPROM() {
  // Check if the magic key is stored in EEPROM
  if (EEPROM.read(MAGIC_KEY_ADDR) != MAGIC_KEY) {
    // If invalid, load default values and save in EEPROM
    initializeSchedule(); // Initialize schedule with default values
    t_sp1 = 17.0; // Default low setpoint
    t_sp2 = 20.0; // Default high setpoint
    saveScheduleToEEPROM();
    saveSetpointsToEEPROM();
    // Save the magic key in the EEPROM
    EEPROM.write(MAGIC_KEY_ADDR, MAGIC_KEY);
  } else {
    // If valid, load the values from the EEPROM
    loadScheduleFromEEPROM();
    loadSetpointsFromEEPROM();
  }
}
NOCOMNCVCCGNDINLED1PWRRelay Module
GND5VSDASCLSQWRTCDS1307+