#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <EEPROM.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
RTC_DS1307 rtc;
// Button pin definitions
const int buttonUpPin = 2;
const int buttonDownPin = 3;
const int buttonSelectPin = 4;
const int buttonBackPin = 9;
const int relay1Pin = 7;
const int relay2Pin = 8;
// EEPROM Address
const int EEPROM_BASE_ADDRESS = 0;
// Schedule structure to store timer settings
struct Schedule {
int hourOn;
int minuteOn;
int hourOff;
int minuteOff;
};
Schedule schedule[7]; // Array to hold the schedule for each day of the week
// Global Variables
int selectedDay = 0; // Track selected day in submenus
bool editing = false; // Flag to determine if the user is editing a value
int mainMenuIndex = 0; // To track main menu navigation
int subMenuIndex = 0; // To track submenu navigation
bool inSubMenu = false;
enum MenuState { MAIN_MENU, SUB_MENU };
MenuState menuState = MAIN_MENU;
void setup() {
lcd.begin(20, 4);
lcd.backlight();
rtc.begin();
// Load settings from EEPROM
loadScheduleFromEEPROM();
pinMode(buttonUpPin, INPUT_PULLUP);
pinMode(buttonDownPin, INPUT_PULLUP);
pinMode(buttonSelectPin, INPUT_PULLUP);
pinMode(buttonBackPin, INPUT_PULLUP);
pinMode(relay1Pin, OUTPUT);
pinMode(relay2Pin, OUTPUT);
digitalWrite(relay1Pin, LOW);
digitalWrite(relay2Pin, LOW);
// Optionally set the RTC to a default time if needed
if (!rtc.isrunning()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Set RTC to the time this code was compiled
}
}
void loop() {
DateTime now = rtc.now();
controlRelays(now);
if (menuState == MAIN_MENU) {
displayMainMenu();
navigateMainMenu();
} else if (menuState == SUB_MENU) {
adjustSchedule();
handleSubMenu();
}
}
// Function to save the schedule to EEPROM
void saveScheduleToEEPROM() {
for (int i = 0; i < 7; i++) {
int address = EEPROM_BASE_ADDRESS + i * sizeof(Schedule);
EEPROM.put(address, schedule[i]);
}
}
// Function to load the schedule from EEPROM
void loadScheduleFromEEPROM() {
for (int i = 0; i < 7; i++) {
int address = EEPROM_BASE_ADDRESS + i * sizeof(Schedule);
EEPROM.get(address, schedule[i]);
}
}
// Function to handle relay control based on the current time
void controlRelays(DateTime now) {
int currentDay = now.dayOfTheWeek();
int currentHour = now.hour();
int currentMinute = now.minute();
bool relay1On = (currentHour > schedule[currentDay].hourOn ||
(currentHour == schedule[currentDay].hourOn && currentMinute >= schedule[currentDay].minuteOn)) &&
(currentHour < schedule[currentDay].hourOff ||
(currentHour == schedule[currentDay].hourOff && currentMinute < schedule[currentDay].minuteOff));
bool relay2On = (currentHour > schedule[currentDay].hourOn ||
(currentHour == schedule[currentDay].hourOn && currentMinute >= schedule[currentDay].minuteOn)) &&
(currentHour < schedule[currentDay].hourOff ||
(currentHour == schedule[currentDay].hourOff && currentMinute < schedule[currentDay].minuteOff));
digitalWrite(relay1Pin, relay1On ? HIGH : LOW);
digitalWrite(relay2Pin, relay2On ? HIGH : LOW);
}
void displayMainMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Set Pump 1 Timer");
lcd.setCursor(0, 1);
lcd.print(" Set Pump 2 Timer");
lcd.setCursor(0, 2);
lcd.print(" View Current Time");
lcd.setCursor(0, 3);
lcd.print(" Pump Status");
lcd.setCursor(0, mainMenuIndex);
lcd.print(">");
delay(500);
}
void navigateMainMenu() {
if (!inSubMenu) {
displayMainMenu();
delay(100); // Short delay for smoother navigation
if (digitalRead(buttonUpPin) == LOW) {
mainMenuIndex--;
if (mainMenuIndex < 0) mainMenuIndex = 3; // Wrap around to last menu item
delay(200); // Debounce delay
} else if (digitalRead(buttonDownPin) == LOW) {
mainMenuIndex++;
if (mainMenuIndex > 3) mainMenuIndex = 0; // Wrap around to first menu item
delay(200); // Debounce delay
} else if (digitalRead(buttonSelectPin) == LOW) {
menuState = SUB_MENU;
inSubMenu = true; // Enter the submenu
delay(200); // Debounce delay
executeSubMenu();
}
}
}
void executeSubMenu() {
switch (mainMenuIndex) {
case 0:
setPump1Timer();
break;
case 1:
setPump2Timer();
break;
case 2:
viewCurrentTime();
break;
case 3:
viewPumpStatus();
break;
}
menuState = MAIN_MENU; // After the submenu is done, return to the main menu
inSubMenu = false; // Exit submenu
}
void viewCurrentTime() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Current Time:");
DateTime now = rtc.now();
lcd.setCursor(0, 1);
lcd.print(now.hour() < 10 ? "0" : "");
lcd.print(now.hour());
lcd.print(":");
lcd.print(now.minute() < 10 ? "0" : "");
lcd.print(now.minute());
lcd.print(":");
lcd.print(now.second() < 10 ? "0" : "");
lcd.print(now.second());
delay(2000);
menuState = MAIN_MENU; // Return to main menu after displaying time
}
void viewPumpStatus() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Pump 1: ");
lcd.print(digitalRead(relay1Pin) == HIGH ? "ON" : "OFF");
lcd.setCursor(0, 1);
lcd.print("Pump 2: ");
lcd.print(digitalRead(relay2Pin) == HIGH ? "ON" : "OFF");
delay(2000);
menuState = MAIN_MENU; // Return to main menu after displaying status
}
void handleSubMenu() {
if (digitalRead(buttonSelectPin) == LOW) {
if (subMenuIndex == 1 || subMenuIndex == 2) {
if (editing) {
subMenuIndex++;
if (subMenuIndex > 4) {
subMenuIndex = 0;
editing = false; // Exit editing mode
}
} else {
editing = true; // Enter editing mode
}
} else {
subMenuIndex++;
if (subMenuIndex > 3) subMenuIndex = 0; // Cycle through submenu items
}
delay(200); // Debounce delay
}
}
void setPump1Timer() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Pump 1 Schedule");
while (inSubMenu) {
displaySchedule(0); // Display the schedule for Pump 1 (assuming index 0)
handleScheduleAdjustment(0); // Handle schedule adjustment for Pump 1
if (digitalRead(buttonBackPin) == LOW) {
inSubMenu = false; // Exit the submenu
saveScheduleToEEPROM(); // Save settings to EEPROM
delay(200); // Debounce delay
}
}
}
void setPump2Timer() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Pump 2 Schedule");
while (inSubMenu) {
displaySchedule(1); // Display the schedule for Pump 2 (assuming index 1)
handleScheduleAdjustment(1); // Handle schedule adjustment for Pump 2
if (digitalRead(buttonBackPin) == LOW) {
inSubMenu = false; // Exit the submenu
saveScheduleToEEPROM(); // Save settings to EEPROM
delay(200); // Debounce delay
}
}
}
// Function to display the schedule for a specific pump
void displaySchedule(int pumpIndex) {
lcd.clear(); // Clear the LCD before updating it
lcd.setCursor(0, 1);
lcd.print("ON: ");
lcd.print(schedule[pumpIndex].hourOn < 10 ? "0" : "");
lcd.print(schedule[pumpIndex].hourOn);
lcd.print(":");
lcd.print(schedule[pumpIndex].minuteOn < 10 ? "0" : "");
lcd.print(schedule[pumpIndex].minuteOn);
lcd.setCursor(0, 2);
lcd.print("OFF: ");
lcd.print(schedule[pumpIndex].hourOff < 10 ? "0" : "");
lcd.print(schedule[pumpIndex].hourOff);
lcd.print(":");
lcd.print(schedule[pumpIndex].minuteOff < 10 ? "0" : "");
lcd.print(schedule[pumpIndex].minuteOff);
delay(250);
}
void adjustSchedule() {
lcd.clear(); // Clear the LCD before updating it
const char* daysOfWeek[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
switch (subMenuIndex) {
case 0:
lcd.setCursor(0, 0);
lcd.print("Set Day: ");
lcd.print(daysOfWeek[selectedDay]);
break;
case 1:
lcd.setCursor(0, 1);
lcd.print("Set ON Hour: ");
lcd.print(schedule[selectedDay].hourOn);
break;
case 2:
lcd.setCursor(0, 1);
lcd.print("Set ON Minute: ");
lcd.print(schedule[selectedDay].minuteOn);
break;
case 3:
lcd.setCursor(0, 2);
lcd.print("Set OFF Hour: ");
lcd.print(schedule[selectedDay].hourOff);
break;
case 4:
lcd.setCursor(0, 2);
lcd.print("Set OFF Minute: ");
lcd.print(schedule[selectedDay].minuteOff);
break;
default:
lcd.setCursor(0, 3);
lcd.print("Complete Schedule");
break;
}
// Handle button presses for adjusting the schedule
if (digitalRead(buttonSelectPin) == LOW) {
subMenuIndex++;
if (subMenuIndex > 4) subMenuIndex = 0; // Wrap around after the last setting
delay(250); // Debounce delay
} else if (digitalRead(buttonUpPin) == LOW) {
increaseSetting();
delay(250); // Debounce delay
} else if (digitalRead(buttonDownPin) == LOW) {
decreaseSetting();
delay(250); // Debounce delay
}
}
void adjustTime(int settingIndex, int increment, int pumpIndex) {
switch (settingIndex) {
case 0: // Adjust hourOn
schedule[pumpIndex].hourOn += increment;
if (schedule[pumpIndex].hourOn < 0) schedule[pumpIndex].hourOn = 23;
if (schedule[pumpIndex].hourOn > 23) schedule[pumpIndex].hourOn = 0;
break;
case 1: // Adjust minuteOn
schedule[pumpIndex].minuteOn += increment;
if (schedule[pumpIndex].minuteOn < 0) schedule[pumpIndex].minuteOn = 59;
if (schedule[pumpIndex].minuteOn > 59) schedule[pumpIndex].minuteOn = 0;
break;
case 2: // Adjust hourOff
schedule[pumpIndex].hourOff += increment;
if (schedule[pumpIndex].hourOff < 0) schedule[pumpIndex].hourOff = 23;
if (schedule[pumpIndex].hourOff > 23) schedule[pumpIndex].hourOff = 0;
break;
case 3: // Adjust minuteOff
schedule[pumpIndex].minuteOff += increment;
if (schedule[pumpIndex].minuteOff < 0) schedule[pumpIndex].minuteOff = 59;
if (schedule[pumpIndex].minuteOff > 59) schedule[pumpIndex].minuteOff = 0;
break;
}
}
// Function to handle schedule adjustments for a specific pump
void handleScheduleAdjustment(int pumpIndex) {
int settingIndex = 0; // 0 = hourOn, 1 = minuteOn, 2 = hourOff, 3 = minuteOff
while (editing) {
if (digitalRead(buttonUpPin) == LOW) {
adjustTime(settingIndex, 1, pumpIndex);
delay(200); // Debounce delay
} else if (digitalRead(buttonDownPin) == LOW) {
adjustTime(settingIndex, -1, pumpIndex);
delay(200); // Debounce delay
} else if (digitalRead(buttonSelectPin) == LOW) {
settingIndex++;
if (settingIndex > 3) {
settingIndex = 0; // Reset to the beginning of the settings
editing = false; // Exit editing mode
}
delay(200); // Debounce delay
}
}
}
void increaseSetting() {
switch (subMenuIndex) {
case 0:
selectedDay = (selectedDay + 1) % 7;
break;
case 1:
schedule[selectedDay].hourOn = (schedule[selectedDay].hourOn + 1) % 24;
break;
case 2:
schedule[selectedDay].minuteOn = (schedule[selectedDay].minuteOn + 1) % 60;
break;
case 3:
schedule[selectedDay].hourOff = (schedule[selectedDay].hourOff + 1) % 24;
break;
case 4:
schedule[selectedDay].minuteOff = (schedule[selectedDay].minuteOff + 1) % 60;
break;
}
}
void decreaseSetting() {
switch (subMenuIndex) {
case 0:
selectedDay = (selectedDay - 1 + 7) % 7;
break;
case 1:
schedule[selectedDay].hourOn = (schedule[selectedDay].hourOn - 1 + 24) % 24;
break;
case 2:
schedule[selectedDay].minuteOn = (schedule[selectedDay].minuteOn - 1 + 60) % 60;
break;
case 3:
schedule[selectedDay].hourOff = (schedule[selectedDay].hourOff - 1 + 24) % 24;
break;
case 4:
schedule[selectedDay].minuteOff = (schedule[selectedDay].minuteOff - 1 + 60) % 60;
break;
}
}