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