//#include <LiquidCrystal.h>
#include <Arduino.h>
#include <Wire.h>
#include <RTClib.h>
#include <Servo.h>
#include <LiquidCrystal_I2C.h>
#include <avr/wdt.h>
// Global variable declarations
DateTime countdownEndTime; // This should be declared only once
RTC_DS3231 rtc;
Servo myServo;
LiquidCrystal_I2C lcd(0x27, 16, 2); // 0x3F , 0x27
//LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
// Pin Definitions
const int readyButtonPin = 8; // 8, 5
const int setTimeButtonPin = 9; // 9, 6
const int plusButtonPin = 11; // 11, 7
const int minusButtonPin = 12; // 12, 8
const int resetButtonPin = 13; // 13, 9
const int servoPin = 10;
// RTC Setup
int countdownDuration[4] = {0, 0, 0, 0}; // Initialize with zeros
// Servo Setup
// State Enumeration
enum State { COUNTDOWN, SET_TIME_MINUTES, SET_TIME_HOURS, SET_TIME_DAYS, SET_TIME_MONTHS, RESET_DELAY };
State currentState = SET_TIME_MINUTES;
unsigned long resetPressedTime = 0; // Tracks when the reset was pressed
const unsigned long resetDelay = 3000; // Delay of 3 seconds
// Time Variables
int setTimes[4] = {0}; // Minutes, Hours, Days, Months
const char* timeUnits[4] = {"m", "H", "D", "M"};
unsigned long lastButtonPress = 0;
int lastSetTimes[4] = {0}; // Last displayed minutes, hours, days, months
State lastState = SET_TIME_MINUTES; // Last displayed state
unsigned long resetStartTime = 0; // Tracks when the reset was initiated
bool resetDelayActive = false; // Indicates if we're in the reset delay period
int daysInMonth(int year, int month) {
// Array of days per month; for February, use index 1 of the next year for leap year calculation
const int monthDays[] = {31, (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return monthDays[month - 1]; // month - 1 because arrays are 0 indexed and months are 1 indexed
}
bool lcdActive = true; // Track whether the LCD is on or off
unsigned long lastActivityTime = 0; // Time since last user activity
const unsigned long lcdTimeout = 300000; // 5 minutes timeout in milliseconds
void debounceAndProcessButtons();
void handleReadyButton();
void handleSetTimeButton();
void handlePlusButton();
void handleMinusButton();
void handleResetButton();
void changeTime();
void addDays();
void addMonths();
void printDateTime();
void startCountdown();
void calculateRemainingTime();
void handleCountdown();
void updateLCD();
void lockBox();
void unlockBox();
void resetBox();
void showWelcomeMessage();
void showError(const char* message);
void setup() {
Serial.begin(9600);
pinMode(readyButtonPin, INPUT);
pinMode(setTimeButtonPin, INPUT);
pinMode(plusButtonPin, INPUT);
pinMode(minusButtonPin, INPUT);
pinMode(resetButtonPin, INPUT);
myServo.attach(servoPin);
lockBox();
//lcd.begin(16, 2);
lcd.init();
lcd.backlight();
showWelcomeMessage();
Serial.println("Initialized");
// Wait for 2 seconds on the welcome screen
delay(2000);
// Now, move to SET_TIME_MINUTES state or display it accordingly
currentState = SET_TIME_MINUTES;
// Optionally, refresh the LCD to show the current state if not automatically handled in loop()
updateLCD(); // This call might need adjustment based on your actual updateLCD function
if (!rtc.begin()) {
showError("RTC Fail");
// Execution will halt here due to showError, so the RTC time won't be set if initialization fails.
}
if (rtc.lostPower()) {
//rtc.adjust(DateTime(2024, 3, 29, 7, 45, 0));
DateTime compileTime(DateTime(F(__DATE__), F(__TIME__)));
rtc.adjust(compileTime);
showError("RTC Reset");
}
}
void loop() {
// Check for any button press to transition from the welcome screen or during normal operation
debounceAndProcessButtons();
// Check if the LCD should be turned off due to inactivity
if (lcdActive && (millis() - lastActivityTime > lcdTimeout)) {
lcdActive = false;
lcd.noBacklight(); // Turn off the LCD backlight
}
// Handle state-specific logic
switch (currentState) {
case COUNTDOWN:
handleCountdown();
break;
case RESET_DELAY:
if (millis() - resetPressedTime >= resetDelay) {
resetBox();
currentState = SET_TIME_MINUTES; // Transition to setting time after delay
}
break;
case SET_TIME_MINUTES:
case SET_TIME_HOURS:
case SET_TIME_DAYS:
case SET_TIME_MONTHS:
break;
}
}
void debounceAndProcessButtons() {
if (millis() - lastButtonPress > 250) { // Debounce interval
bool buttonPressed = false;
if (digitalRead(readyButtonPin) == HIGH) {
buttonPressed = true;
handleReadyButton();
Serial.println("Ready Button");
}
if (digitalRead(setTimeButtonPin) == HIGH) {
buttonPressed = true;
handleSetTimeButton();
Serial.println("Set Button");
}
if (digitalRead(plusButtonPin) == HIGH) {
buttonPressed = true;
handlePlusButton();
Serial.println("Plus Button");
}
if (digitalRead(minusButtonPin) == HIGH) {
buttonPressed = true;
handleMinusButton();
Serial.println("Minus Button");
}
if (digitalRead(resetButtonPin) == HIGH) {
buttonPressed = true;
handleResetButton();
Serial.println("Reset Button");
}
// If any button was pressed, update the lastButtonPress time
if (buttonPressed) {
lastButtonPress = millis();
lastActivityTime = millis(); // Reset the inactivity timer
if (!lcdActive) { // Wake up the LCD if it was off
lcdActive = true;
lcd.backlight(); // Assuming your LCD has this function to turn on the backlight
updateLCD(); // Refresh the LCD display
}
}
}
}
void handleReadyButton() {
if (currentState != COUNTDOWN && lcdActive) { // Ensure LCD is active before starting countdown
startCountdown();
}
}
void handleSetTimeButton() {
if (currentState != COUNTDOWN) {
// Cycle through SET_TIME states, skipping COUNTDOWN
currentState = static_cast<State>((currentState + 1) % 5); // Assuming COUNTDOWN is not included in the cycle
if (currentState == COUNTDOWN) { // If COUNTDOWN is somehow reached, skip to SET_TIME_MINUTES
currentState = SET_TIME_MINUTES;
}
updateLCD();
}
}
void handlePlusButton() {
if (currentState >= SET_TIME_MINUTES && currentState <= SET_TIME_MONTHS && currentState != COUNTDOWN) {
changeTime(1); // Increment the current setting
updateLCD(); // Reflect the change on display
}
}
void handleMinusButton() {
if (currentState >= SET_TIME_MINUTES && currentState <= SET_TIME_MONTHS && currentState != COUNTDOWN) {
changeTime(-1); // Decrement the current setting
updateLCD(); // Reflect the change on display
}
}
void handleResetButton() {
// Example of calling resetBox when the reset button is detected as pressed
if (digitalRead(resetButtonPin) == HIGH) {
resetBox(); // Call the reset logic
// Note: Ensure this action is debounced or conditionally triggered to avoid repeated resets
}
}
void changeTime(int increment) {
switch (currentState) {
case SET_TIME_MINUTES:
setTimes[0] = (setTimes[0] + increment + 60) % 60;
break;
case SET_TIME_HOURS:
setTimes[1] = (setTimes[1] + increment + 24) % 24;
break;
case SET_TIME_DAYS:
// Adjusted to ensure wrapping includes 0
setTimes[2] = setTimes[2] + increment;
if (setTimes[2] < 0) setTimes[2] = 30; // Assuming max 31 days for simplicity
else if (setTimes[2] > 30) setTimes[2] = 0;
break;
case SET_TIME_MONTHS:
// Adjusted to ensure wrapping includes 0
setTimes[3] = (setTimes[3] + increment + 12) % 12;
break;
}
updateLCD(); // Reflect the change on display
}
void addDays(DateTime &date, int daysToAdd) {
while (daysToAdd > 0) {
int daysInCurrentMonth = daysInMonth(date.year(), date.month());
if (date.day() + daysToAdd > daysInCurrentMonth) {
// Going to the next month
daysToAdd -= (daysInCurrentMonth - date.day() + 1); // Calculate remaining days after finishing this month
if (date.month() == 12) {
// Going to the next year
date = DateTime(date.year() + 1, 1, 1);
} else {
// Going to the next month of the same year
date = DateTime(date.year(), date.month() + 1, 1);
}
} else {
// Adding days within the same month
date = DateTime(date.year(), date.month(), date.day() + daysToAdd, date.hour(), date.minute(), date.second());
daysToAdd = 0; // No more days to add
}
}
}
void addMonths(DateTime &date, int monthsToAdd) {
int year = date.year();
int month = date.month();
int day = date.day();
month += monthsToAdd;
while (month > 12) {
year++;
month -= 12;
}
// Correct the day if we've moved into a month with fewer days.
day = min(day, daysInMonth(year, month));
date = DateTime(year, month, day, date.hour(), date.minute(), date.second());
}
// Ensure this is declared before startCountdown so it can be used
void addMonths(DateTime &date, int monthsToAdd);
void printDateTime(const DateTime& dt) {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second());
Serial.println(buffer);
}
void startCountdown() {
DateTime now = rtc.now();
// Print the current time
Serial.print("Current time: ");
printDateTime(now);
// Add days and months to the current time
addDays(now, setTimes[2]);
addMonths(now, setTimes[3]);
// Print the time after adding days and months
Serial.print("Time after adding days and months: ");
printDateTime(now);
// Calculate the end time by adding hours and minutes
countdownEndTime = now + TimeSpan(0, setTimes[1], setTimes[0], 0);
// Print the final countdown end time
Serial.print("Countdown end time: ");
printDateTime(countdownEndTime);
// Enter countdown state and lock the box
currentState = COUNTDOWN;
lockBox();
// Debugging output
//Serial.println("Countdown started and box locked.");
}
void calculateRemainingTime() {
DateTime now = rtc.now();
if (now < countdownEndTime) {
TimeSpan remainingTime = countdownEndTime - now;
countdownDuration[0] = remainingTime.minutes() % 60;
countdownDuration[1] = remainingTime.hours() % 24;
countdownDuration[2] = remainingTime.days() % 30; // Simplistic, assumes 30 days per month
countdownDuration[3] = remainingTime.days() / 30; // Simplistic, for demonstration
} else {
// Countdown has ended, reset countdownDuration and possibly take other actions
for (int i = 0; i < 4; i++) countdownDuration[i] = 0;
// Here, you can also change the state to indicate the countdown has finished, unlock the box, etc.
}
}
void handleCountdown() {
static unsigned long lastRefreshTime = 0;
unsigned long currentTime = millis();
if (currentTime - lastRefreshTime >= 1000) { // Update every second
DateTime now = rtc.now();
if (now < countdownEndTime) {
// Calculate full months left by comparing years and months
int monthsLeft = (countdownEndTime.year() - now.year()) * 12 + (countdownEndTime.month() - now.month());
// If days in end time are less than current day, subtract one month
if (countdownEndTime.day() < now.day()) {
monthsLeft--;
}
// Calculate the days left not counting full months
int daysLeft = countdownEndTime.day() - now.day();
if (daysLeft < 0) {
// If days are negative, we need to find out how many days are in the previous month
int previousMonth = now.month() - 1;
if (previousMonth == 0) {
previousMonth = 12;
}
daysLeft += daysInMonth(now.year(), previousMonth);
}
// Calculate the hours, minutes, and seconds left
int hoursLeft = countdownEndTime.hour() - now.hour();
int minutesLeft = countdownEndTime.minute() - now.minute();
int secondsLeft = countdownEndTime.second() - now.second();
if (secondsLeft < 0) {
minutesLeft--;
secondsLeft += 60;
}
if (minutesLeft < 0) {
hoursLeft--;
minutesLeft += 60;
}
if (hoursLeft < 0) {
daysLeft--;
hoursLeft += 24;
}
if (daysLeft < 0) {
// It's possible that days are still negative if countdownEndTime is on a new month
monthsLeft--;
daysLeft += daysInMonth(now.year(), now.month());
}
// Ensure monthsLeft doesn't go negative
if (monthsLeft < 0) {
monthsLeft = 0;
}
// Update the LCD with remaining time
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("Countdown");
lcd.setCursor(0, 1);
char buffer[20];
sprintf(buffer, "%02dm:%02dH:%02dD:%02dM", minutesLeft, hoursLeft, daysLeft, monthsLeft);
lcd.print(buffer);
lastRefreshTime = currentTime;
} else {
// Countdown has finished, perform desired actions
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("The box is");
lcd.setCursor(4, 1);
lcd.print("Unlocked");
unlockBox(); // Unlock the box at the end of the countdown
currentState = SET_TIME_MINUTES; // Return to setting minutes after countdown
}
}
}
void lockBox() {
myServo.write(0); // Assume 0 is the locked position
Serial.println("Box Locked");
}
void unlockBox() {
myServo.write(90); // Assume 90 is the unlocked position
Serial.println("Box Unlocked");
}
void resetBox() {
// Clear time settings
for (int &time : setTimes) {
time = 0; // Reset each component of the setTimes array to 0
}
// Reset the countdown end time
countdownEndTime = DateTime(); // Or use a specific reset value if needed
// Reset the current state of the application
currentState = SET_TIME_MINUTES;
unlockBox(); // Assume this sets the servo to the locked position
Serial.println("System Reset");
// Reinitialize the LCD display
lcd.init(); // Reinitialize the LCD in case it's needed
lcd.backlight(); // Ensure the backlight is on
showWelcomeMessage(); // Display the initial welcome message again
// Reset other global variables as necessary
lastButtonPress = 0;
resetPressedTime = 0;
updateLCD();
}
void showWelcomeMessage() {
lcd.clear();
lcd.setCursor(4, 0);
lcd.print("Lock Box");
lcd.setCursor(5, 1);
lcd.print("V.1.0");
}
void showError(const char* message) {
lcd.clear();
lcd.print(message);
while (1); // Halt execution
}
void updateLCD() {
lcd.clear(); // Clear the LCD to ensure that no old text is displayed
// Debugging: Print the current state to the serial monitor
Serial.print("Current State: ");
Serial.println(currentState);
switch (currentState) {
case SET_TIME_MINUTES:
case SET_TIME_HOURS:
case SET_TIME_DAYS:
case SET_TIME_MONTHS:
// Debugging: Print current setTimes values to the serial monitor
Serial.print("SET TIME Values: ");
Serial.println(String(setTimes[0]) + "m:" + String(setTimes[1]) + "h:" + String(setTimes[2]) + "d:" + String(setTimes[3]) + "M");
lcd.setCursor(0, 0);
lcd.print("SET TIME:");
// Move cursor to the second row
lcd.setCursor(0, 1);
lcd.print(String(setTimes[0]) + "m:" + String(setTimes[1]) + "h:" + String(setTimes[2]) + "d:" + String(setTimes[3]) + "M");
break;
case COUNTDOWN:
// Debugging: Print countdownDuration values to the serial monitor
Serial.print("COUNTDOWN Values: ");
Serial.println(String(countdownDuration[0]) + "m:" + String(countdownDuration[1]) + "h:" + String(countdownDuration[2]) + "d:" + String(countdownDuration[3]) + "M");
// Code to display the countdown; you would fill in the actual countdown values here
lcd.setCursor(0, 0);
lcd.print("Countdown:");
// Move cursor to the second row
lcd.setCursor(0, 1);
lcd.print(String(countdownDuration[0]) + "m:" + String(countdownDuration[1]) + "h:" + String(countdownDuration[2]) + "d:" + String(countdownDuration[3]) + "M");
break;
// Add other cases for other states as needed
default:
// Default message or state handling
lcd.setCursor(0, 0);
lcd.print("Ready");
break;
}
}