#include <Wire.h> // Include I2C library for LCD
#include <LiquidCrystal_I2C.h> // Include I2C LCD library
#include <I2C_RTC.h> // Include RTC library
#include <EEPROM.h> // Include EEPROM library
#include <Servo.h> // Include Servo library
#include <LowPower.h> // Include Low Power library
// Define LCD address and button pins
const int lcd_address = 0x27; // Replace with your I2C LCD address if different
const int btnEdit = 2; // Enter edit mode or confirm edit
const int btnIncr = 3; // Increase by 30 mins
const int btnDecr = 4; // Decrease by 30 mins
const int btnFeed = 5; // Instantly start feeding mechanism
const int alarmPin = 7; // connected to RTC's SQW for alarm interrupt
// Define servo pin and feeding duration
const int servoPin = 11;
const int feedingDuration = 1000; // 1 second in milliseconds
// Define intervals/timers (ms)
const int blinkInterval = 500; // 0.5 secs
const int sleepDelay = 10000; // Time it takes before sleep: 5 mins
const int lcdNobacklightDelay = 5000; // Time it takes before turning off LCD backlight: 10 secs
// Define LCD text blink variables
unsigned long lastEditingBlinkTime = millis();
unsigned long lastWakeUp = millis();
unsigned long lastLcdBacklight = millis();
bool showEditingText = true;
// Define variables
LiquidCrystal_I2C lcd(lcd_address, 16, 2); // Initialize I2C LCD
DS1307 rtc; // Create RTC object
Servo servo; // Create Servo object
// Stores feeding time in half-hours (0 to 47 for 23:30)
int feedingTime = 0; // Needs adjustment during initialization
int rtc_minutes = 0;
// Flags
boolean alreadyFed = false; // Already fed at a certain time, don't feed again
char freshStart = 'y'; // Is the device starting for the first time?
boolean inEditMode = false;
boolean backlightOn = false;
void setup() {
Serial.begin(9600); // Optional: Enable serial communication for debugging
Serial.println("HI");
pinMode(btnEdit, INPUT_PULLUP);
pinMode(btnIncr, INPUT_PULLUP);
pinMode(btnDecr, INPUT_PULLUP);
pinMode(btnFeed, INPUT_PULLUP);
loadFromEEPROM();
initLcd();
initRtc();
displayTime();
}
void loop() {
managePower();
// Track how many buttons pressed
int buttonsPressed = 0;
// Edit feeding time
buttonsPressed += checkBtnEdit();
buttonsPressed += checkBtnIncr();
buttonsPressed += checkBtnDecr();
buttonsPressed += checkBtnFeed();
if (inEditMode) {
blinkEditingText();
} else {
checkFeedingTime();
}
// Check if buttons are pressed and LCD's backlight is off
if (buttonsPressed > 0) {
displayTime();
}
// Update display if time changed (minute precision)
int rtc_minutes_now = rtc.getMinutes();
if (rtc_minutes != rtc_minutes_now) {
displayTime();
rtc_minutes = rtc_minutes_now;
}
delay(100); // Debounce buttons and avoid busy loop
}
void managePower() {
if (backlightOn) {
if (shouldNoBacklight()) {
lcd.noBacklight();
backlightOn = false;
}
}
trySleeping();
}
void preSleep() {
attachInterrupt(digitalPinToInterrupt(btnEdit), wakeUp, LOW);
attachInterrupt(digitalPinToInterrupt(alarmPin), wakeUp2, LOW);
lcd.noDisplay();
}
void postSleep() {
detachInterrupt(digitalPinToInterrupt(btnEdit));
detachInterrupt(digitalPinToInterrupt(alarmPin));
lcd.display();
startLcd();
lastWakeUp = millis();
}
void trySleeping() {
unsigned long currentTime = millis();
if (currentTime - lastWakeUp >= sleepDelay) {
preSleep();
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
postSleep();
}
}
boolean shouldNoBacklight() {
boolean goNoBacklight = false;
unsigned long currentTime = millis();
if (currentTime - lastLcdBacklight >= lcdNobacklightDelay) {
goNoBacklight = true;
}
return goNoBacklight;
}
void startLcd() {
lcd.backlight(); // Turn on LCD backlight
backlightOn = true;
lastLcdBacklight = millis();
}
void initLcd() {
lcd.init();
startLcd();
}
void initRtc() {
if (!rtc.begin()) {
Serial.println("Couldn't find RTC!");
lcd.println("Couldn't find RTC!");
while (1);
}
rtc.setHourMode(CLOCK_H24);
rtc_minutes = rtc.getMinutes();
if (freshStart == 'y') {
rtc.setDateTime(__DATE__, __TIME__);
}
}
// Load variables from EEPROM (if saved previously)
void loadFromEEPROM() {
freshStart = EEPROM.read(2);
if (freshStart == 'n') {
feedingTime = EEPROM.read(0);
alreadyFed = EEPROM.read(1);
} else {
freshStart = 'y'; // only once
EEPROM.write(2, freshStart); // afterwards, load from this 'n' instead
}
}
void wakeUp() {
Serial.println("Wake Up from Edit Button");
}
void wakeUp2() {
Serial.println("Wake Up from Alarm");
}
void blinkEditingText() {
unsigned long currentTime = millis();
if (currentTime - lastEditingBlinkTime >= blinkInterval) {
showEditingText = !showEditingText;
lastEditingBlinkTime = currentTime;
// Move the cursor to the position of "[Editing]" text
lcd.setCursor(7, 0);
if (showEditingText) {
lcd.print("[Editing]");
} else {
lcd.print(" "); // Clear it
}
// Set the cursor position back to origin
lcd.setCursor(0, 0);
}
}
int checkBtnEdit() {
int pressed = 0;
if (!digitalRead(btnEdit)) {
delay(50); // Debounce button press
if (!digitalRead(btnEdit)) {
pressed = 1;
if (inEditMode) {
inEditMode = false;
saveFeedingTime();
} else {
inEditMode = true;
}
startLcd();
}
}
return pressed;
}
int checkBtnIncr() {
int pressed = 0;
if (!digitalRead(btnIncr)) {
pressed = 1;
delay(50); // Debounce button press
if (!digitalRead(btnIncr)) {
startLcd();
if (inEditMode) {
feedingTime = (feedingTime + 1) % 48; // Increase feeding time (wrap around)
}
}
}
return pressed;
}
int checkBtnDecr() {
int pressed = 0;
if (!digitalRead(btnDecr)) {
pressed = 1;
delay(50); // Debounce button press
if (!digitalRead(btnDecr)) {
startLcd();
if (inEditMode) {
feedingTime = (feedingTime - 1 + 48) % 48; // Decrease feeding time (wrap around)
}
}
}
return pressed;
}
int checkBtnFeed() {
int pressed = 0;
if (!digitalRead(btnFeed) && !inEditMode) {
pressed = 1;
delay(50); // Debounce button press
if (!digitalRead(btnFeed)) {
startLcd();
if (!inEditMode) {
startFeeding();
}
}
}
return pressed;
}
void checkFeedingTime() {
// Check if current minute matches the feeding time (every 30 minutes)
if (rtc.getMinutes() % 30 == 0 && rtc.getHours() == getHourFromFeedingTime()) {
if (!alreadyFed) {
startFeeding();
alreadyFed = true;
EEPROM.write(1, true);
}
} else {
alreadyFed = false; // Reset flag if feeding time doesn't match and alreadyFed is true
EEPROM.write(1, false);
}
}
void startFeeding() {
lcd.clear();
lcd.print("Feeding...");
servo.attach(servoPin); // Attach servo to pin
servo.write(180); // Rotate servo to dispense food
delay(feedingDuration);
servo.write(0); // Return servo to initial position
delay(500); // Make sure the servo finished rotating
servo.detach(); // Detach servo to save power
displayTime();
}
int getHourFromFeedingTime() {
// Convert from half-hours to hours
return feedingTime / 2;
}
void printTimeAndFeeding() {
int minutes = rtc.getMinutes();
int hours = rtc.getHours();
// Real Time
if (hours < 10) {
Serial.print("0");
}
Serial.print(hours);
Serial.print(":");
if (minutes < 10) {
Serial.print("0");
}
Serial.println(minutes);
// Feeding Time
hours = getHourFromFeedingTime();
Serial.print("Feed Time: ");
if (hours < 10) {
Serial.print("0");
}
Serial.print(hours);
Serial.print(":");
if (feedingTime % 2 == 0) {
Serial.println("00");
} else {
Serial.println("30");
}
Serial.println("");
}
void displayTime() {
// Display current time and feeding time on LCD
lcd.clear();
int minutes = rtc.getMinutes();
int hours = rtc.getHours();
// Real Time
if (hours < 10) {
lcd.print("0");
}
lcd.print(hours);
lcd.print(":");
if (minutes < 10) {
lcd.print("0");
}
lcd.print(minutes);
// Feeding Time
lcd.setCursor(0, 1);
lcd.print("Feed Time: ");
hours = getHourFromFeedingTime();
if (hours < 10) {
lcd.print("0");
}
lcd.print(hours);
lcd.print(":");
if (feedingTime % 2 == 0) {
lcd.print("00");
} else {
lcd.print("30");
}
}
void saveFeedingTime() {
// Save feeding time to EEPROM
EEPROM.write(0, feedingTime);
}