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