/*
  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 "Adafruit_GFX.h"
#include "Adafruit_SSD1306.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
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

// OLED Display Instance
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// 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();
  }

  // Initialize the SSD1306 display
  if (!initializeDisplay()) {
    Serial.println(F("SSD1306 allocation failed"));
    while (true);
  }

  // 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 initializeDisplay() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("Error: SSD1306 allocation failed. Check connections!"));
    return false;
  }
  return true;
}

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

void displaySplashScreen() {
  display.clearDisplay();
  display.drawBitmap(0, -10, splash, 128, 64, WHITE);
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 46);
  display.print(F("  CHRONO-THERMOSTAT"));
  display.setCursor(0, 56);
  display.print(F("(c) Pablo Galdo, 2024"));
  display.display();
  delay(2000);
  display.clearDisplay();
}

// ========== 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() {
  display.ssd1306_command(SSD1306_DISPLAYOFF);
  displayOn = false;
}

// Function to turn on the display
void turnOnDisplay() {
  display.ssd1306_command(SSD1306_DISPLAYON);
  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() {
  // If the delay has not started, initiate it
  if (!displayUpdateComplete) {
    if (millis() - displayStartTime >= displayDelay) {
      display.display();
      display.clearDisplay();
      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() {

  display.clearDisplay();

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  display.setCursor(2, 2);
  if (now.day() < 10) {
    display.print(F("0"));
  }
  display.print(now.day(), DEC);
  display.print(F("/"));
  if (now.month() < 10) {
    display.print(F("0"));
  }
  display.print(now.month(), DEC);
  display.print(F("/"));
  display.println(now.year(), DEC);

  display.setCursor(2, 12);
  display.setTextSize(2);

  currentColonBlinkMillis = millis();

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

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

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

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

  display.setCursor(2, 32);
  display.setTextSize(1);

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

  display.setCursor(74, 2);
  display.setTextSize(1);
  display.print(F("TEMP."));

  display.setCursor(74, 12);
  display.setTextSize(2);
  display.print(t_pv, 1);
  display.setTextSize(1);
  display.println(F("C"));

  display.setCursor(74, 32);
  display.print(F("SP:"));
  display.print(t_sp, 1);
  display.println(F("C"));

  currentRelayBlinkMillis = millis();

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

  display.setCursor(34, 52);
  if (relayState) {
    if (relayVisible) {
      display.print(F("HEATER  ON"));
    }
  } else {
    display.print(F("HEATER OFF"));
  }

  display.display();
}

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

  display.clearDisplay();

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  display.setCursor(0, 0);
  display.println(F("TEMPERATURE  SETPOINT"));

  display.setCursor(0, 16);
  display.print(F("SP LOW"));
  if (editingSetpoint == EDIT_SP1) {
    display.print(F(" > "));
  } else {
    display.print(F("   "));
  }
  display.setTextSize(2);
  display.print(t_sp1, 1);
  display.setTextSize(1);
  display.print(F("C"));

  display.setCursor(0, 40);
  display.print(F("SP HI "));
  if (editingSetpoint == EDIT_SP2) {
    display.print(F(" > "));
  } else {
    display.print(F("   "));
  }
  display.setTextSize(2);
  display.print(t_sp2, 1);
  display.setTextSize(1);
  display.print(F("C"));

  display.display();
}

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

  display.clearDisplay();

  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(12, 2);
  display.println(F("SCHEDULER"));

  display.setTextSize(2);
  display.setCursor(12, 14);
  switch (navDay) {
    case 0:
      display.print(F("SUNDAY"));
      break;
    case 1:
      display.print(F("MONDAY"));
      break;
    case 2:
      display.print(F("TUESDAY"));
      break;
    case 3:
      display.print(F("WEDNESDAY"));
      break;
    case 4:
      display.print(F("THURSDAY"));
      break;
    case 5:
      display.print(F("FRIDAY"));
      break;
    case 6:
      display.print(F("SATURDAY"));
      break;
  }

  display.setTextSize(1);
  display.setCursor(16, 36);
  display.println(F("HORA:"));

  display.setTextSize(2);
  display.setCursor(16, 46);
  display.print(navHour);

  display.setTextSize(1);
  display.setCursor(80, 36);
  display.println(F("SP:"));

  display.setTextSize(2);
  display.setCursor(80, 46);
  display.print(schedule[navDay][navHour] ? F("HI") : F("LOW"));

  display.display();
}

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

  display.clearDisplay();

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  display.setCursor(0, 0);
  display.println(F("DATE & TIME ADJUST."));

  display.setTextSize(2);
  display.setCursor(0, 15);

  if (WatchState == DAY) {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
  }
  if (now.day() < 10) {
    display.print("0");
  }
  display.print(now.day(), DEC);
  display.setTextColor(SSD1306_WHITE);
  display.print(F("/"));

  if (WatchState == MONTH) {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
  }
  if (now.month() < 10) {
    display.print("0");
  }
  display.print(now.month(), DEC);
  display.setTextColor(SSD1306_WHITE);
  display.print(F("/"));

  if (WatchState == YEAR) {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
  }
  display.print(now.year(), DEC);
  display.setTextColor(SSD1306_WHITE);
  display.println();

  display.setCursor(0, 36);
  if (WatchState == HOUR) {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
  }
  if (now.hour() < 10) {
    display.print("0");
  }
  display.print(now.hour(), DEC);
  display.setTextColor(SSD1306_WHITE);
  display.print(F(":"));

  if (WatchState == MINUTE) {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
  }
  if (now.minute() < 10) {
    display.print("0");
  }
  display.print(now.minute(), DEC);
  display.setTextColor(SSD1306_WHITE);

  display.display();
}

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+