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

  Project in editing process. Operation may be limited.

  A touchscreen in needed in order to
  activate two buttons simultaneously.


  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"

// EEPROM Addresses for Settings
#define EEPROM_ADDR_SP1 0     // EEPROM address for Setpoint 1
#define EEPROM_ADDR_SP2 4     // EEPROM address for Setpoint 2
#define EEPROM_ADDR_STATES 8  // EEPROM address for time states

// Splash Logo
#include "splash.h"

// Display Settings
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

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

// 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 hysteresys
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 DayOfWeek
{
  SUNDAY,
  MONDAY,
  TUESDAY,
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY
};

//DayOfWeek selectedDay = SUNDAY; // Initially selected Sunday

// Function to Check the State for a Specific Hour and Day
bool stateForHourAndDay(int hour);

// 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;

// Number of States for Time-Dependent Settings
const int numStates = 24;

// Array to Store Time States
bool states[numStates] = {false};

// 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()
{
  // Set the output mode for the relay pin
  pinMode(OUT_RELAY, OUTPUT);
  
  // Disable relay output at start
  digitalWrite(OUT_RELAY, LOW);

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

  pinMode(IN_UP, INPUT);
  // Deboucing
  DB_UP.attach(IN_UP);
  DB_UP.interval(dbintervalupdown); // Interval (ms)

  pinMode(IN_DOWN, INPUT);
  // Deboucing
  DB_DOWN.attach(IN_DOWN);
  DB_DOWN.interval(dbintervalupdown); // Interval (ms)

  // Begin serial communication with a baud rate of 9600
  Serial.begin(9600);

  // Attempt to retrieve values from EEPROM
  if (!loadEEPROMValues()) {
    // Values are not valid, set default values and save to EEPROM
    setDefaultValues();
  }

  // Initialize the SSD1306 display
  if (!initializeDisplay()) {
    // Display initialization failed, enter an infinite loop
    Serial.println(F("SSD1306 allocation failed"));
    while (true);
  }

  // Initialize the real-time clock (RTC)
  if (!initializeRTC()) {
    // RTC initialization failed, print an error message and abort
    Serial.println("Couldn't find RTC");
    Serial.flush();
    abort();
  }

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

// Check if the provided setpoint value is within the valid range.
bool isValidSetpoint(float value)
{
  return (value >= 15.0) && (value <= 30.0);
}

// Check if all states in the array are valid (true or false).
bool isValidStates(bool states[], int size)
{
  // Check if all states are valid (true or false)
  for (int i = 0; i < size; ++i)
  {
    if (states[i] != true && states[i] != false)
    {
      return false; // One state is not valid
    }
  }
  return true; // All states are valid
}

// Function to load values from EEPROM and check their validity
bool loadEEPROMValues()
{
  EEPROM.get(EEPROM_ADDR_SP1, t_sp1);
  EEPROM.get(EEPROM_ADDR_SP2, t_sp2);
  EEPROM.get(EEPROM_ADDR_STATES, states);

  return isValidSetpoint(t_sp1) && isValidSetpoint(t_sp2) && isValidStates(states, numStates);
}

// Function to set default values and save them to EEPROM
void setDefaultValues()
{
  t_sp1 = 18.0;
  t_sp2 = 20.0;

  // Initialize time states as desired
  for (int i = 0; i < numStates; ++i) {
    states[i] = false;
  }

  // Save default values to EEPROM
  EEPROM.put(EEPROM_ADDR_SP1, t_sp1);
  EEPROM.put(EEPROM_ADDR_SP2, t_sp2);
  EEPROM.put(EEPROM_ADDR_STATES, states);
}

// Function to initialize the SSD1306 display
bool initializeDisplay()
{
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
        Serial.println(F("Error: SSD1306 allocation failed. Check connections!"));
        return false; // Display initialization failed
    }
    return true; // Display initialized successfully
}

// Function to initialize the real-time clock (RTC)
bool initializeRTC()
{
    if (!rtc.begin()) {
        Serial.println(F("Error: Couldn't find RTC. Check connections!"));
        return false;
    }
    return true;
}

// Function to display the splash screen for 2 seconds
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, 2023"));
  display.display();
  delay(2000);
  // Clear the display after the splash screen
  display.clearDisplay();
}

// Get the current state based on the current time and day.
bool getCurrentState()
{
  // Get the current time from the RTC module
  now = rtc.now();
  // Extract the current hour
  int currentHour = now.hour();
  // Check the state for the current hour
  return stateForHourAndDay(currentHour);
}

// ========== 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
  }

  bool currentState = getCurrentState();  // Get the current state
  now = rtc.now();  // Get the current date and time from the RTC

    // 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
  t_sp = currentState ? t_sp2 : 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 the delay time has passed
    if (millis() - displayStartTime >= displayDelay)
    {
      // Update the OLED display
      display.display();
      display.clearDisplay();

      // Mark the delay as complete
      displayUpdateComplete = true;
    }
  }
  else
  {
    // Perform necessary tasks while waiting
    // ...

    // Reset the delay for the next iteration
    displayStartTime = millis();
    displayUpdateComplete = false;
  }
}

// Handle main menu interactions.
void handleMainMenu()
{
  editingSetpoint = EDIT_SP1;
  dateTimeState = 0; // Reset the time state to 0 when the display is off
  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) {
        // Switch to the setpoint menu
        lastDelayTime = millis();
        menuState = SETPOINT;
        buttonPressed = true;
      }
    }
    // Check if menu and down button are pressed
  } else if (displayOn == true && DB_IN_MENU == 1 && DB_IN_DOWN == 1 && DB_IN_UP == 0) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        // Switch to the setpoint menu
        lastDelayTime = millis();
        menuState = TIME_STATE;
        buttonPressed = true;
      }
    }
    // Check if menu and up button are pressed
  } else if (displayOn == true && DB_IN_MENU == 1 && DB_IN_DOWN == 0 && DB_IN_UP == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        // Switch to the setpoint menu
        lastDelayTime = millis();
        menuState = DATETIME;
        buttonPressed = true;
      }
    }
  } else {
    buttonPressed = false;
  }
}

// Handle setpoint menu interactions.
void handleSetpointMenu()
{
  // Check if display is off by inactivity
  if (displayOn == false) {
    menuState = MAIN; // Return to the main menu
  }
  // Check if menu button is pressed
  if (DB_IN_MENU == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        // Toggle between editing SP1 and SP2
        if (editingSetpoint == EDIT_SP1) {
          editingSetpoint = EDIT_SP2;
        } else {
          // Save setpoint values to EEPROM when exiting the setpoint menu
          EEPROM.put(EEPROM_ADDR_SP1, t_sp1);
          EEPROM.put(EEPROM_ADDR_SP2, t_sp2);

          // Switch to the main menu
          editingSetpoint = EDIT_SP2;
          menuState = MAIN;
        }
        lastDelayTime = millis();
        buttonPressed = true;
      }
    }
  } else {
    buttonPressed = false;
  }

  // Check if down button is pressed
  if (DB_IN_DOWN == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      // Increment or decrement the selected setpoint based on the editing mode
      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();
    }
  }

  // Check if up button is pressed
  if (DB_IN_UP == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      // Increment or decrement the selected setpoint based on the editing mode
      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()
{
  // Check if display is off by inactivity
  if (displayOn == false) {
    menuState = MAIN; // Return to the main menu
  }

  // Check if menu button is pressed
  if (DB_IN_MENU == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        // Save time states to EEPROM and switch to the main menu
        EEPROM.put(EEPROM_ADDR_STATES, states);
        lastDelayTime = millis();
        menuState = MAIN;
        buttonPressed = true;
      }
    }
  } else {
    buttonPressed = false;
  }

  // Check if down button is pressed
  if (DB_IN_DOWN == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      // Toggle between ON and OFF for the current state
      states[dateTimeState] = !states[dateTimeState];
      lastDelayTime = millis();
    }
  }

  // Check if up button is pressed
  if (DB_IN_UP == 1) {
    if (millis() - lastDelayTime > buttonsDelay) {
      // Increment the current hour state
      dateTimeState = (dateTimeState + 1) % numStates;
      lastDelayTime = millis();
    }
  }
}

// Handle date and time adjustment menu interactions.
void handleDateTimeMenu()
{
  // Check if display is off by inactivity
  if (displayOn == false) {
    WatchState = DAY; // Reset the date and time adjustment state to DAY when the display is off
    menuState = MAIN; // Return to the main menu
  }

  // Code to handle the date and time adjustment menu
  if (DB_IN_MENU == 1) { // Menu button (activate menu and confirm)
    if (millis() - lastDelayTime > buttonsDelay) {
      if (!buttonPressed) {
        switch (WatchState) {
          case DAY:
            WatchState = MONTH; // Navigate to month setting
            break;
          case MONTH:
            WatchState = YEAR; // Navigate to year setting
            break;
          case YEAR:
            WatchState = HOUR; // Navigate to hour setting
            break;
          case HOUR:
            WatchState = MINUTE; // Navigate to minute setting
            break;
          case MINUTE:
            menuState = MAIN; // Return to the main menu
            break;
        }
        lastDelayTime = millis();
        buttonPressed = true;
      }
    }
  } else {
    buttonPressed = false; // Button released, allow changes on the next press
  }

  // Check if down button is pressed
  if (DB_IN_DOWN == 1) { // Down button (navigate to the previous and decrease values)
    if (millis() - lastDelayTime > buttonsDelay) {
      // Decrement the value of the current field
      decrementDateTimeField();
      lastDelayTime = millis();
    }
  }

  // Check if up button is pressed
  if (DB_IN_UP == 1) { // Up Button (navigate to the next and increase values)
    if (millis() - lastDelayTime > buttonsDelay) {
      // Increment the value of the current field
      incrementDateTimeField();
      lastDelayTime = millis();
    }
  }
}

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

// Decrement the specified field of the date and time.
void decrementDateTimeField()
{
  DateTime now = rtc.now();
  switch (WatchState)
  {
    case DAY:
      // Decrement the day
      rtc.adjust(DateTime(now.year(), now.month(), (now.day() == 1) ? 31 : now.day() - 1, now.hour(), now.minute(), now.second()));
      break;
    case MONTH:
      // Decrement the month
      rtc.adjust(DateTime(now.year(), (now.month() == 1) ? 12 : now.month() - 1, now.day(), now.hour(), now.minute(), now.second()));
      break;
    case YEAR:
      // Decrement the year
      rtc.adjust(DateTime((now.year() == 2000) ? 2099 : now.year() - 1, now.month(), now.day(), now.hour(), now.minute(), now.second()));
      break;
    case HOUR:
      // Decrement the hour
      rtc.adjust(DateTime(now.year(), now.month(), now.day(), (now.hour() == 0) ? 23 : now.hour() - 1, now.minute(), now.second()));
      break;
    case MINUTE:
      // Decrement the 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()
{

  // Set text size and color for OLED display
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  // Display current date (smaller font, centered)
  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 current time (larger font)
  display.setCursor(2, 12);
  display.setTextSize(2);

  // Get elapsed time in milliseconds
  currentColonBlinkMillis = millis();

  // Blink every 500 ms
  if (currentColonBlinkMillis - lastColonBlinkMillis >= 500) {
    // Toggle colon visibility
    colonVisible = !colonVisible;
    lastColonBlinkMillis = currentColonBlinkMillis;
  }

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

  // Show or hide the colons based on the current state
  if (colonVisible) {
    display.print(F(":"));
  } else {
    display.print(F(" "));
  }

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

    // Display day of the week
  display.setCursor(2, 32);
  display.setTextSize(1);

  // Use the enum DayOfWeek to get the day name
  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 PV label (smaller font)
  display.setCursor(74, 2);
  display.setTextSize(1);
  display.print(F("TEMP."));

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

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

  // Display relay state
  
  // Get elapsed time in milliseconds
  currentRelayBlinkMillis = millis();

  // Blink every 250 ms
  if (currentRelayBlinkMillis - lastRelayBlinkMillis >= 250) {
    // Toggle relay status visibility
    relayVisible = !relayVisible;
    lastRelayBlinkMillis = currentRelayBlinkMillis;
  }

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

// Display the setpoint menu on the OLED.
void displaySetpointScreen()
{
  // Set display parameters
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  // Set the cursor position for the menu title
  display.setCursor(0, 0);

  // Display the menu title
  display.println(F("TEMPERATURE SETPOINT"));

  // Display Setpoint 1
  display.setCursor(0, 16);
  display.print(F("SP LOW"));
  if (editingSetpoint == EDIT_SP1)
  {
    display.print(F(" >")); // Highlight the editing setpoint
  } else {
    display.print(F("  "));
  }
  display.print(F("  "));
  display.setTextSize(2);
  display.print(t_sp1, 1);
  display.setTextSize(1);
  display.print(F("C"));

  // Display Setpoint 2
  display.setCursor(0, 40);
  display.print(F("SP HI "));
  if (editingSetpoint == EDIT_SP2)
  {
    display.print(F(" >")); // Highlight the editing setpoint
  } else {
    display.print(F("  "));
  }
  display.print(F("  "));
  display.setTextSize(2);
  display.print(t_sp2, 1);
  display.setTextSize(1);
  display.print(F("C"));
}

// Display the states and their status on the screen.
void displayTimeStateScreen()
{
  // Set display parameters
  display.setTextSize(1);  // Small font size for the title
  display.setTextColor(SSD1306_WHITE);

  // Set the cursor position for the menu title
  display.setCursor(0, 0);
  // Display the menu title
  display.print(F("STATES"));

  // Set font size for state entries
  display.setTextSize(1);
  // Set cursor position for state entries
  display.setCursor(0, 16);

  // Calculate the start and end indices based on the current dateTimeState
  int startIndex = max(0, dateTimeState - 3);
  int endIndex = min(numStates - 1, startIndex + 6);

  // Iterate through the states and display them
  for (int i = startIndex; i <= endIndex; i++)
  {
    display.print(F(" "));
    if (i < 10)
    {
      display.print(F("0")); // Pad with a leading zero for single-digit indices
    }
    display.print(i, DEC);
    display.print(F(" "));

    if (i == dateTimeState)
    {
      display.print(F("> ")); // Highlight the current state
    }
    else
    {
      display.print(F("  "));
    }

    // Display the state (HIGH or LOW)
    display.println(states[i] ? F("HIGH") : F("LOW"));
  }
}

// Check if there is a specific state for the given hour.
bool stateForHourAndDay(int hour)
{
  // Calculate the index based on the hour (within the appropriate range)
  int index = hour % numStates;
  // Check if the state is ON for the calculated index and selected day
  return states[index];
}

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

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

  // Set the cursor position for the menu title
  display.setCursor(0, 0);
  // Display the menu title
  display.println(F("DATE & TIME ADJUST."));

  // Set larger font size for date and time fields
  display.setTextSize(2);
  // Adjust text position to fit the screen width
  display.setCursor(0, 15);

  // Display Day field
  if (WatchState == DAY)
  {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Highlight the current field
  }
  if (now.day() < 10)
  {
    display.print("0");
  }
  display.print(now.day(), DEC);
  display.setTextColor(SSD1306_WHITE);

  display.print(F("/"));

  // Display Month field
  if (WatchState == MONTH)
  {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Highlight the current field
  }
  if (now.month() < 10)
  {
    display.print("0");
  }
  display.print(now.month(), DEC);
  display.setTextColor(SSD1306_WHITE);

  display.print(F("/"));

  // Display Year field
  if (WatchState == YEAR)
  {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Highlight the current field
  }
  display.print(now.year(), DEC);
  display.setTextColor(SSD1306_WHITE);
  display.println();

  // Set cursor position for the Hour field
  display.setCursor(0, 36);
  // Display Hour field
  if (WatchState == HOUR)
  {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Highlight the current field
  }
  if (now.hour() < 10)
  {
    display.print("0");
  }
  display.print(now.hour(), DEC);
  display.setTextColor(SSD1306_WHITE);

  display.print(F(":"));

  // Display Minute field
  if (WatchState == MINUTE)
  {
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Highlight the current field
  }
  if (now.minute() < 10)
  {
    display.print("0");
  }
  display.print(now.minute(), DEC);
  display.setTextColor(SSD1306_WHITE);
}

// Function to execute actions based on the current menu state
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;
  }
}
NOCOMNCVCCGNDINLED1PWRRelay Module
GND5VSDASCLSQWRTCDS1307+