RTC, LCD, TTL, Fingerprint together
Summary:
This ESP32-based fingerprint attendance system uses string student IDs like M00814431, automatically mapping them to internal numeric IDs for the fingerprint sensor. It supports two user types: students and professors, with only one professor allowed in the system. The enrollment process first asks for the user type, then for a student ID or professor name, followed by fingerprint capture requiring two successful finger placements. Deletion requires placing the finger to identify the user, then confirmation via serial input. Professors are authenticated by fingerprint to start or stop attendance, set the RTC time manually, or clear all attendance records. Attendance sessions run for a fixed duration (default 10 minutes) with a live countdown on the LCD. During an active session, students place their fingerprints to mark attendance, while a professor can place their finger again to end the session early. The system prevents duplicate marking within the same session or on the same date. All data is stored in SPIFFS: user records in users.txt and attendance logs in attendance.txt. The RTC DS3231 maintains time, with initial NTP sync over WiFi (SSID and password hardcoded) and fallback to manual setting via professor authentication. The LCD displays the current time and date when idle, and shows prompts, status messages, and attendance countdown during operations. The fingerprint sensor communicates over hardware serial, and the LCD uses I2C. The main menu offers enrollment, deletion, attendance mode, time setting, and record clearing. Timeout returns to the main menu after inactivity except during active attendance. The system checks for existing professor before enrolling a new one, verifies student ID uniqueness, and maintains an internal list of up to 128 users and 500 attendance logs. When attendance ends, a summary prints to serial. The implementation handles communication errors, sensor failures, and invalid inputs gracefully, with clear LCD feedback and serial prompts throughout.
Output:
Code:
/***************************************************
Complete Fingerprint Attendance System for ESP32
Features:
- String Student IDs (e.g., M00814431)
- Auto-mapping to internal numeric IDs
- Enroll with type first (Student/Professor)
- Delete requires fingerprint confirmation
- Professor authentication to start/stop attendance
- Professor authentication to set RTC time
- Attendance timer with countdown display
- Clear attendance records (requires professor fingerprint)
- Real-time clock with NTP sync and manual set
- LCD display with timestamp
- Data stored in SPIFFS
****************************************************/
#include <Adafruit_Fingerprint.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <SPIFFS.h>
#include <WiFi.h>
#include <time.h>
// I2C LCD Setup (Try 0x27 or 0x3F)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// RTC Setup
RTC_DS3231 rtc;
const char* ssid = "WPAI-5G";
const char* password = "irhaarham05";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;
// Attendance Timer Settings (in minutes)
const int attendanceDuration = 10; // 10 minutes attendance window
// Fingerprint Sensor Setup for ESP32
HardwareSerial mySerial(2);
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);
// System Variables
bool attendanceActive = false;
bool professorAuthenticated = false;
int currentMode = 0;
unsigned long lastActivity = 0;
const unsigned long inactivityTimeout = 120000; // 2 minutes for attendance mode
unsigned long attendanceStartTime = 0;
unsigned long attendanceEndTime = 0;
int remainingSeconds = 0;
// Data structures
struct UserData {
String studentId;
String name;
String type;
uint8_t fpId;
bool active;
};
UserData users[128];
int userCount = 0;
uint8_t nextAvailableFPId = 1;
// Attendance log for current session
struct AttendanceRecord {
String studentId;
String name;
bool marked;
};
AttendanceRecord currentSession[128];
int sessionCount = 0;
// Permanent attendance log
struct AttendanceLog {
String studentId;
String name;
String timestamp;
String date;
};
AttendanceLog logs[500];
int logCount = 0;
// Current enrollment variables
String currentEnrollType = "";
String currentEnrollStudentId = "";
String currentEnrollName = "";
uint8_t currentEnrollFPId = 0;
int enrollStep = 0;
// Time set variables
bool timeSetModeActive = false;
bool timeSetAuthenticated = false;
String pendingTimeString = "";
bool manualTimeSet = false;
// Delete mode variables
bool deleteModeActive = false;
bool deleteConfirmed = false;
uint8_t deleteFPId = 0;
String deleteStudentId = "";
String deleteName = "";
unsigned long deleteStartTime = 0;
// Clear records mode variables
bool clearModeActive = false;
bool clearConfirmed = false;
unsigned long clearStartTime = 0;
void setup() {
Serial.begin(115200);
delay(100);
// Initialize SPIFFS for data storage
if (!SPIFFS.begin(true)) {
Serial.println("SPIFFS Mount Failed");
return;
}
// Initialize LCD
Wire.begin(21, 22);
lcd.init();
lcd.backlight();
// Initialize RTC
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
lcd.setCursor(0, 0);
lcd.print("RTC Error!");
while (1);
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Initializing...");
lcd.setCursor(0, 1);
lcd.print("RTC & WiFi");
// Initialize RTC time
initializeRTC();
// Initialize Fingerprint Sensor
mySerial.begin(57600, SERIAL_8N1, 16, 17);
finger.begin(57600);
delay(100);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Initializing...");
lcd.setCursor(0, 1);
lcd.print("Fingerprint...");
if (finger.verifyPassword()) {
Serial.println("Fingerprint sensor found!");
lcd.setCursor(0, 1);
lcd.print("Sensor OK! ");
delay(1000);
} else {
Serial.println("Fingerprint sensor not found!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Sensor Error!");
lcd.setCursor(0, 1);
lcd.print("Check Wiring!");
while (1);
}
// Load user data from SPIFFS
loadUserData();
loadAttendanceLogs();
findNextAvailableFPId();
// Get sensor info
finger.getParameters();
finger.getTemplateCount();
Serial.println("\n=========================================");
Serial.println(" SYSTEM INITIALIZATION COMPLETE");
Serial.println("=========================================");
Serial.print("Total fingerprints stored in sensor: ");
Serial.println(finger.templateCount);
Serial.print("Total users in database: ");
Serial.println(userCount);
Serial.print("Next available internal ID: ");
Serial.println(nextAvailableFPId);
Serial.print("Total attendance records: ");
Serial.println(logCount);
// Display current RTC time
displayCurrentTime();
showMainMenu();
}
void initializeRTC() {
DateTime now = rtc.now();
if (now.year() < 2024 || now.year() > 2030) {
Serial.println("RTC has invalid time! Setting from NTP...");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Syncing Time...");
if (syncWithNTP()) {
Serial.println("Time synced successfully from NTP!");
} else {
Serial.println("Failed to sync with NTP. Please set time manually.");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Time via");
lcd.setCursor(0, 1);
lcd.print("Professor FP");
delay(3000);
}
} else {
Serial.println("RTC has valid time");
displayCurrentTime();
}
}
bool syncWithNTP() {
Serial.print("Connecting to WiFi");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Connecting WiFi");
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
lcd.setCursor(0, 1);
lcd.print(".");
attempts++;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\nWiFi connection failed!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Failed!");
return false;
}
Serial.println("\nWiFi connected!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Connected");
lcd.setCursor(0, 1);
lcd.print("Getting Time...");
delay(1000);
configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org", "time.nist.gov");
struct tm timeinfo;
int retry = 0;
while (!getLocalTime(&timeinfo) && retry < 10) {
Serial.print(".");
delay(1000);
retry++;
}
if (retry < 10) {
rtc.adjust(DateTime(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec));
Serial.println("\nTime synced from NTP!");
displayCurrentTime();
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
return true;
} else {
Serial.println("\nFailed to get time from NTP!");
return false;
}
}
void setManualTime(String timeString) {
if (timeString.length() < 19) {
Serial.println("Invalid format! Use: YYYY-MM-DD HH:MM:SS");
Serial.print("Enter time: ");
return;
}
int year = timeString.substring(0, 4).toInt();
int month = timeString.substring(5, 7).toInt();
int day = timeString.substring(8, 10).toInt();
int hour = timeString.substring(11, 13).toInt();
int minute = timeString.substring(14, 16).toInt();
int second = timeString.substring(17, 19).toInt();
if (year >= 2024 && year <= 2030 && month >= 1 && month <= 12 && day >= 1 && day <= 31 &&
hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59 && second >= 0 && second <= 59) {
rtc.adjust(DateTime(year, month, day, hour, minute, second));
Serial.println("Time set manually!");
displayCurrentTime();
manualTimeSet = false;
showMainMenu();
} else {
Serial.println("Invalid time values! Please try again.");
Serial.println("Format: YYYY-MM-DD HH:MM:SS");
Serial.print("Enter: ");
}
}
void displayCurrentTime() {
DateTime now = rtc.now();
Serial.println("\n+---------------------------------------+");
Serial.print("| Current RTC Time: ");
Serial.print(now.year());
Serial.print("/");
Serial.print(now.month());
Serial.print("/");
Serial.print(now.day());
Serial.print(" ");
Serial.print(now.hour());
Serial.print(":");
Serial.print(now.minute());
Serial.print(":");
Serial.print(now.second());
String ampm = (now.hour() < 12) ? " AM" : " PM";
Serial.print(ampm);
Serial.print(" (");
switch(now.dayOfTheWeek()) {
case 0: Serial.print("Sunday"); break;
case 1: Serial.print("Monday"); break;
case 2: Serial.print("Tuesday"); break;
case 3: Serial.print("Wednesday"); break;
case 4: Serial.print("Thursday"); break;
case 5: Serial.print("Friday"); break;
case 6: Serial.print("Saturday"); break;
}
Serial.println(")");
Serial.println("+---------------------------------------+");
}
void updateClockDisplay() {
DateTime now = rtc.now();
lcd.setCursor(0, 0);
lcd.print(" ");
lcd.setCursor(0, 0);
char timeStr[9];
sprintf(timeStr, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
lcd.print(timeStr);
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
char dateStr[11];
sprintf(dateStr, "%02d/%02d/%04d", now.day(), now.month(), now.year());
lcd.print(dateStr);
}
void showMainMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("1.Enroll 2.Delete");
lcd.setCursor(0, 1);
lcd.print("3.Att 4.SetTime");
Serial.println("\n=========================================");
Serial.println(" FINGERPRINT ATTENDANCE SYSTEM");
Serial.println("=========================================");
Serial.println(" 1. Enroll New Fingerprint");
Serial.println(" 2. Delete Fingerprint");
Serial.println(" 3. Start Attendance Mode");
Serial.println(" 4. Set RTC Time (Professor Only)");
Serial.println(" 5. Clear Attendance Records");
Serial.println("=========================================");
Serial.print("\nEnter your choice (1-5): ");
}
void handleMainMenu(String input) {
if (input == "1") {
currentMode = 1;
enrollStep = 0;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enroll Mode");
lcd.setCursor(0, 1);
lcd.print("Type: S/P");
Serial.println("\n=========================================");
Serial.println(" ENROLLMENT MODE");
Serial.println("=========================================");
Serial.println("Select fingerprint type:");
Serial.println("S - STUDENT");
Serial.println("P - PROFESSOR");
Serial.println("Type 'QUIT' to return to menu");
Serial.print("Choice: ");
}
else if (input == "2") {
currentMode = 2;
deleteModeActive = true;
deleteConfirmed = false;
deleteStartTime = millis();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Delete Mode");
lcd.setCursor(0, 1);
lcd.print("Place finger...");
Serial.println("\n=========================================");
Serial.println(" DELETION MODE");
Serial.println("=========================================");
Serial.println("Place finger on sensor to delete");
Serial.println("Type 'QUIT' to return to menu");
}
else if (input == "3") {
if (!attendanceActive) {
currentMode = 3;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Authenticating");
lcd.setCursor(0, 1);
lcd.print("Place Professor FP");
Serial.println("\n=========================================");
Serial.println(" ATTENDANCE MODE");
Serial.println("=========================================");
Serial.println("Please authenticate with Professor fingerprint");
Serial.println("Place finger on sensor...");
}
}
else if (input == "4") {
currentMode = 4;
timeSetModeActive = true;
timeSetAuthenticated = false;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set RTC Time");
lcd.setCursor(0, 1);
lcd.print("Place Professor FP");
Serial.println("\n=========================================");
Serial.println(" SET RTC TIME");
Serial.println("=========================================");
Serial.println("Professor authentication required to set time.");
Serial.println("Place finger on sensor...");
}
else if (input == "5") {
currentMode = 5;
clearModeActive = true;
clearConfirmed = false;
clearStartTime = millis();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Clear Records");
lcd.setCursor(0, 1);
lcd.print("Place Professor FP");
Serial.println("\n=========================================");
Serial.println(" CLEAR ATTENDANCE RECORDS");
Serial.println("=========================================");
Serial.println("WARNING: This will delete ALL attendance records!");
Serial.println("Fingerprints and user data will remain unchanged.");
Serial.println("Please authenticate with Professor fingerprint to proceed.");
Serial.println("Place finger on sensor...");
}
else {
Serial.println("Invalid choice! Please enter 1, 2, 3, 4, or 5");
}
}
void handleTimeSetMode() {
if (!timeSetModeActive) return;
if (!timeSetAuthenticated) {
int p = finger.getImage();
if (p == FINGERPRINT_OK) {
p = finger.image2Tz();
if (p == FINGERPRINT_OK) {
p = finger.fingerFastSearch();
if (p == FINGERPRINT_OK) {
String userType = getUserTypeByFPId(finger.fingerID);
if (userType == "PROFESSOR") {
timeSetAuthenticated = true;
Serial.println("\nProfessor authenticated successfully!");
Serial.println("Enter date and time in format:");
Serial.println("YYYY-MM-DD HH:MM:SS");
Serial.println("Example: 2026-04-02 14:30:00");
Serial.print("Enter: ");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Professor Auth");
lcd.setCursor(0, 1);
lcd.print("Enter Time");
} else {
Serial.println("Not a professor! Access denied");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Access Denied!");
lcd.setCursor(0, 1);
lcd.print("Professor only");
delay(2000);
timeSetModeActive = false;
currentMode = 0;
showMainMenu();
}
} else {
Serial.println("Fingerprint not recognized!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Not Recognized");
lcd.setCursor(0, 1);
lcd.print("Try Again");
delay(2000);
}
}
} else if (p == FINGERPRINT_NOFINGER) {
static unsigned long lastWaitingMsg = 0;
if (millis() - lastWaitingMsg > 5000) {
lastWaitingMsg = millis();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set RTC Time");
lcd.setCursor(0, 1);
lcd.print("Place Professor FP");
}
}
}
// Handle time input after authentication
if (timeSetAuthenticated && Serial.available()) {
String timeInput = Serial.readStringUntil('\n');
timeInput.trim();
// Parse time string: "2026-04-02 14:30:00"
if (timeInput.length() >= 19) {
int year = timeInput.substring(0, 4).toInt();
int month = timeInput.substring(5, 7).toInt();
int day = timeInput.substring(8, 10).toInt();
int hour = timeInput.substring(11, 13).toInt();
int minute = timeInput.substring(14, 16).toInt();
int second = timeInput.substring(17, 19).toInt();
if (year >= 2024 && year <= 2030 && month >= 1 && month <= 12 && day >= 1 && day <= 31 &&
hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59 && second >= 0 && second <= 59) {
rtc.adjust(DateTime(year, month, day, hour, minute, second));
Serial.println("Time set successfully!");
displayCurrentTime();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Time Set!");
lcd.setCursor(0, 1);
lcd.print("Success");
delay(2000);
} else {
Serial.println("Invalid time values! Please try again.");
Serial.println("Format: YYYY-MM-DD HH:MM:SS");
Serial.print("Enter: ");
return;
}
} else {
Serial.println("Invalid format! Use: YYYY-MM-DD HH:MM:SS");
Serial.print("Enter: ");
return;
}
timeSetModeActive = false;
currentMode = 0;
showMainMenu();
}
// Timeout after 30 seconds
if (timeSetModeActive && !timeSetAuthenticated && (millis() - clearStartTime > 30000)) {
Serial.println("\nTimeout: Returning to main menu");
timeSetModeActive = false;
currentMode = 0;
showMainMenu();
}
}
void handleEnrollment(String input) {
if (input.equalsIgnoreCase("QUIT")) {
currentMode = 0;
enrollStep = 0;
showMainMenu();
return;
}
if (enrollStep == 0) {
if (input.equalsIgnoreCase("S")) {
currentEnrollType = "STUDENT";
enrollStep = 1;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enroll STUDENT");
lcd.setCursor(0, 1);
lcd.print("Enter Student ID");
Serial.print("Enter Student ID (e.g., M00814431): ");
}
else if (input.equalsIgnoreCase("P")) {
if (isProfessorExists()) {
Serial.println("Professor already enrolled!");
Serial.println("Only one professor allowed in the system");
Serial.println("Enrollment cancelled");
enrollStep = 0;
currentMode = 0;
showMainMenu();
return;
}
currentEnrollType = "PROFESSOR";
enrollStep = 2;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enroll PROFESSOR");
lcd.setCursor(0, 1);
lcd.print("Enter Full Name");
Serial.print("Enter Professor's Full Name: ");
}
else {
Serial.println("Invalid! Enter 'S' for STUDENT or 'P' for PROFESSOR");
}
}
else if (enrollStep == 1) {
currentEnrollStudentId = input;
if (isStudentIdUsed(currentEnrollStudentId)) {
Serial.print("Student ID ");
Serial.print(currentEnrollStudentId);
Serial.println(" is already registered!");
Serial.print("Enter Student ID: ");
return;
}
enrollStep = 2;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ID: ");
lcd.print(currentEnrollStudentId.substring(0, 12));
lcd.setCursor(0, 1);
lcd.print("Enter Name");
Serial.print("Enter full name for ");
Serial.print(currentEnrollStudentId);
Serial.print(": ");
}
else if (enrollStep == 2) {
currentEnrollName = input;
currentEnrollFPId = nextAvailableFPId;
Serial.print("Assigned internal ID: ");
Serial.println(currentEnrollFPId);
Serial.println("Ready to enroll fingerprint...");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enrolling...");
lcd.setCursor(0, 1);
lcd.print("Place finger");
delay(1000);
performEnrollment();
}
}
void performEnrollment() {
if (enrollFingerprint(currentEnrollFPId)) {
saveUserData(currentEnrollStudentId, currentEnrollName, currentEnrollType, currentEnrollFPId);
Serial.println("\nENROLLMENT SUCCESSFUL");
Serial.print("Type: ");
Serial.println(currentEnrollType);
if (currentEnrollType == "STUDENT") {
Serial.print("Student ID: ");
Serial.println(currentEnrollStudentId);
}
Serial.print("Name: ");
Serial.println(currentEnrollName);
Serial.print("Internal FP ID: ");
Serial.println(currentEnrollFPId);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Success!");
lcd.setCursor(0, 1);
lcd.print(currentEnrollName.substring(0, 14));
delay(3000);
findNextAvailableFPId();
} else {
Serial.println("\nENROLLMENT FAILED");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enrollment");
lcd.setCursor(0, 1);
lcd.print("Failed!");
delay(3000);
}
currentEnrollType = "";
currentEnrollStudentId = "";
currentEnrollName = "";
currentEnrollFPId = 0;
enrollStep = 0;
currentMode = 0;
showMainMenu();
}
bool enrollFingerprint(uint8_t fpId) {
int p = -1;
Serial.println("\nStep 1: Place finger on sensor");
while (p != FINGERPRINT_OK) {
p = finger.getImage();
switch (p) {
case FINGERPRINT_OK:
Serial.println("Image taken");
break;
case FINGERPRINT_NOFINGER:
Serial.print(".");
delay(100);
break;
case FINGERPRINT_PACKETRECIEVEERR:
Serial.println("Communication error");
return false;
case FINGERPRINT_IMAGEFAIL:
Serial.println("Imaging error");
return false;
}
}
p = finger.image2Tz(1);
if (p != FINGERPRINT_OK) {
Serial.println("Failed to convert image");
return false;
}
Serial.println("First image converted");
Serial.println("Step 2: Remove finger");
delay(2000);
p = 0;
while (p != FINGERPRINT_NOFINGER) {
p = finger.getImage();
}
Serial.println("Finger removed");
Serial.println("Step 3: Place same finger again");
p = -1;
while (p != FINGERPRINT_OK) {
p = finger.getImage();
switch (p) {
case FINGERPRINT_OK:
Serial.println("Second image taken");
break;
case FINGERPRINT_NOFINGER:
Serial.print(".");
delay(100);
break;
}
}
p = finger.image2Tz(2);
if (p != FINGERPRINT_OK) {
Serial.println("Failed to convert second image");
return false;
}
Serial.println("Second image converted");
p = finger.createModel();
if (p != FINGERPRINT_OK) {
if (p == FINGERPRINT_ENROLLMISMATCH)
Serial.println("Fingerprints did not match");
else
Serial.println("Failed to create model");
return false;
}
Serial.println("Fingerprints matched");
p = finger.storeModel(fpId);
if (p != FINGERPRINT_OK) {
Serial.println("Failed to store fingerprint");
return false;
}
Serial.println("Fingerprint stored successfully!");
return true;
}
void startAttendance() {
attendanceActive = true;
attendanceStartTime = millis();
attendanceEndTime = attendanceStartTime + (attendanceDuration * 60 * 1000);
remainingSeconds = attendanceDuration * 60;
sessionCount = 0;
Serial.println("\nATTENDANCE SESSION STARTED");
Serial.print("Duration: ");
Serial.print(attendanceDuration);
Serial.println(" minutes");
Serial.println("Students can now place their fingers");
Serial.println("Professor can place finger again to end early");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Attendance ON");
updateCountdownDisplay();
}
void updateCountdownDisplay() {
if (!attendanceActive) return;
int minutes = remainingSeconds / 60;
int seconds = remainingSeconds % 60;
lcd.setCursor(0, 1);
lcd.print("Time Left: ");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
}
void stopAttendance() {
attendanceActive = false;
professorAuthenticated = false;
Serial.println("\nATTENDANCE SESSION ENDED");
Serial.println("\n+---------------------------------------+");
Serial.println("| ATTENDANCE SUMMARY |");
Serial.println("+---------------------------------------+");
Serial.print("| Total Students Present: ");
Serial.print(sessionCount);
for(int i = 0; i < 10; i++) Serial.print(" ");
Serial.println("|");
Serial.println("+---------------------------------------+");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Session Ended");
lcd.setCursor(0, 1);
lcd.print("Students: ");
lcd.print(sessionCount);
delay(3000);
currentMode = 0;
showMainMenu();
}
void markAttendance(String studentId, String name) {
DateTime now = rtc.now();
char timeStr[9];
sprintf(timeStr, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
char dateStr[11];
sprintf(dateStr, "%02d/%02d/%04d", now.day(), now.month(), now.year());
for (int i = 0; i < sessionCount; i++) {
if (currentSession[i].studentId == studentId) {
Serial.print("\n");
Serial.print(name);
Serial.println(" already marked attendance in this session!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Already Marked");
lcd.setCursor(0, 1);
lcd.print(name.substring(0, 15));
delay(2000);
return;
}
}
if (isAttendanceRecorded(studentId, dateStr)) {
Serial.print("\n");
Serial.print(name);
Serial.println(" already marked attendance today!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Already Marked");
lcd.setCursor(0, 1);
lcd.print(name.substring(0, 15));
delay(2000);
return;
}
currentSession[sessionCount].studentId = studentId;
currentSession[sessionCount].name = name;
currentSession[sessionCount].marked = true;
sessionCount++;
saveAttendanceLog(studentId, name, timeStr, dateStr);
Serial.println("\n+---------------------------------------+");
Serial.println("| ATTENDANCE RECORDED |");
Serial.println("+---------------------------------------+");
Serial.print("| Student ID: ");
Serial.print(studentId);
for(int i = studentId.length(); i < 19; i++) Serial.print(" ");
Serial.println("|");
Serial.print("| Name: ");
Serial.print(name);
for(int i = name.length(); i < 25; i++) Serial.print(" ");
Serial.println("|");
Serial.print("| Time: ");
Serial.print(timeStr);
Serial.print(" | Date: ");
Serial.print(dateStr);
Serial.println(" |");
Serial.println("+---------------------------------------+");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ID: ");
lcd.print(studentId.substring(0, 12));
lcd.setCursor(0, 1);
lcd.print("Name: ");
lcd.print(name.substring(0, 11));
delay(3000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Attendance ON");
updateCountdownDisplay();
}
void handleClearRecordsMode() {
if (!clearModeActive) return;
if (!clearConfirmed) {
int p = finger.getImage();
if (p == FINGERPRINT_OK) {
p = finger.image2Tz();
if (p == FINGERPRINT_OK) {
p = finger.fingerFastSearch();
if (p == FINGERPRINT_OK) {
String userType = getUserTypeByFPId(finger.fingerID);
if (userType == "PROFESSOR") {
Serial.println("\nProfessor authenticated successfully!");
Serial.println("WARNING: This will delete ALL attendance records!");
Serial.print("Type 'CONFIRM' to proceed or anything else to cancel: ");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Professor Auth");
lcd.setCursor(0, 1);
lcd.print("Confirm? CONFIRM");
clearConfirmed = true;
clearStartTime = millis();
} else {
Serial.println("Not a professor! Access denied");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Access Denied!");
lcd.setCursor(0, 1);
lcd.print("Professor only");
delay(2000);
clearModeActive = false;
currentMode = 0;
showMainMenu();
}
} else {
Serial.println("Fingerprint not recognized!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Not Recognized");
lcd.setCursor(0, 1);
lcd.print("Try Again");
delay(2000);
}
}
} else if (p == FINGERPRINT_NOFINGER) {
static unsigned long lastWaitingMsg = 0;
if (millis() - lastWaitingMsg > 5000) {
lastWaitingMsg = millis();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Clear Records");
lcd.setCursor(0, 1);
lcd.print("Place Professor FP");
}
}
}
if (clearConfirmed && Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
if (input == "CONFIRM") {
if (SPIFFS.exists("/attendance.txt")) {
if (SPIFFS.remove("/attendance.txt")) {
Serial.println("\nAttendance records deleted");
} else {
Serial.println("Failed to delete attendance records");
}
}
logCount = 0;
for (int i = 0; i < 500; i++) {
logs[i].studentId = "";
logs[i].name = "";
logs[i].timestamp = "";
logs[i].date = "";
}
sessionCount = 0;
for (int i = 0; i < 128; i++) {
currentSession[i].studentId = "";
currentSession[i].name = "";
currentSession[i].marked = false;
}
Serial.println("\nALL ATTENDANCE RECORDS CLEARED");
Serial.println("Fingerprints and user data remain unchanged.");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Records Cleared");
lcd.setCursor(0, 1);
lcd.print("Success!");
delay(3000);
} else {
Serial.println("\nOperation cancelled. Attendance records preserved.");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Cancelled");
lcd.setCursor(0, 1);
lcd.print("Records Kept");
delay(2000);
}
clearModeActive = false;
currentMode = 0;
showMainMenu();
}
if (clearModeActive && !clearConfirmed && (millis() - clearStartTime > 30000)) {
Serial.println("\nTimeout: Returning to main menu");
clearModeActive = false;
currentMode = 0;
showMainMenu();
}
}
void handleAttendanceMode() {
if (!attendanceActive) {
int p = finger.getImage();
if (p == FINGERPRINT_OK) {
p = finger.image2Tz();
if (p == FINGERPRINT_OK) {
p = finger.fingerFastSearch();
if (p == FINGERPRINT_OK) {
String userType = getUserTypeByFPId(finger.fingerID);
if (userType == "PROFESSOR") {
startAttendance();
} else {
Serial.println("Not a professor! Access denied");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Access Denied!");
lcd.setCursor(0, 1);
lcd.print("Professor only");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Authenticating");
lcd.setCursor(0, 1);
lcd.print("Place Professor FP");
}
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Not Recognized");
lcd.setCursor(0, 1);
lcd.print("Try Again");
delay(1500);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Authenticating");
lcd.setCursor(0, 1);
lcd.print("Place Professor FP");
}
}
} else if (p == FINGERPRINT_NOFINGER) {
static unsigned long lastWaitingMsg = 0;
if (millis() - lastWaitingMsg > 5000) {
lastWaitingMsg = millis();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Authenticating");
lcd.setCursor(0, 1);
lcd.print("Place Professor FP");
}
}
} else {
unsigned long currentTime = millis();
if (currentTime >= attendanceEndTime) {
stopAttendance();
return;
}
remainingSeconds = (attendanceEndTime - currentTime) / 1000;
updateCountdownDisplay();
static unsigned long lastScan = 0;
if (millis() - lastScan > 500) {
lastScan = millis();
int p = finger.getImage();
if (p == FINGERPRINT_OK) {
p = finger.image2Tz();
if (p == FINGERPRINT_OK) {
p = finger.fingerFastSearch();
if (p == FINGERPRINT_OK) {
String userType = getUserTypeByFPId(finger.fingerID);
if (userType == "PROFESSOR") {
Serial.println("\nProfessor ending attendance session early");
stopAttendance();
return;
} else if (userType == "STUDENT") {
String studentId = getStudentIdByFPId(finger.fingerID);
String name = getNameByFPId(finger.fingerID);
markAttendance(studentId, name);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Attendance ON");
updateCountdownDisplay();
}
}
}
}
}
}
}
void handleDeleteMode() {
if (!deleteModeActive) return;
if (!deleteConfirmed) {
int p = finger.getImage();
if (p == FINGERPRINT_OK) {
p = finger.image2Tz();
if (p == FINGERPRINT_OK) {
p = finger.fingerFastSearch();
if (p == FINGERPRINT_OK) {
deleteFPId = finger.fingerID;
for (int i = 0; i < userCount; i++) {
if (users[i].fpId == deleteFPId && users[i].active) {
deleteStudentId = users[i].studentId;
deleteName = users[i].name;
break;
}
}
Serial.print("\nFingerprint recognized!\n");
Serial.print("Student ID: ");
Serial.println(deleteStudentId);
Serial.print("Name: ");
Serial.println(deleteName);
Serial.print("Type: ");
Serial.println(getUserTypeByFPId(deleteFPId));
Serial.print("\nConfirm deletion of ");
Serial.print(deleteName);
Serial.println("? (Y/N): ");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Delete: ");
lcd.print(deleteName.substring(0, 12));
lcd.setCursor(0, 1);
lcd.print("Confirm? Y/N");
deleteConfirmed = true;
deleteStartTime = millis();
} else {
Serial.println("Fingerprint not found in database!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Not Found!");
lcd.setCursor(0, 1);
lcd.print("Try Again");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Delete Mode");
lcd.setCursor(0, 1);
lcd.print("Place finger...");
}
}
} else if (p == FINGERPRINT_NOFINGER) {
static unsigned long lastWaitingMsg = 0;
if (millis() - lastWaitingMsg > 5000) {
lastWaitingMsg = millis();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Delete Mode");
lcd.setCursor(0, 1);
lcd.print("Place finger...");
}
}
}
if (deleteConfirmed && Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
if (input.equalsIgnoreCase("Y")) {
uint8_t p = finger.deleteModel(deleteFPId);
if (p == FINGERPRINT_OK) {
deleteUserData(deleteStudentId);
Serial.print("\nDELETED\n");
Serial.print("Student ID: ");
Serial.println(deleteStudentId);
Serial.print("Name: ");
Serial.println(deleteName);
Serial.println("has been deleted");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Deleted!");
lcd.setCursor(0, 1);
lcd.print(deleteName.substring(0, 14));
delay(3000);
findNextAvailableFPId();
} else {
Serial.println("Deletion failed!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Delete Failed!");
delay(2000);
}
deleteModeActive = false;
currentMode = 0;
showMainMenu();
}
else if (input.equalsIgnoreCase("N") || input.equalsIgnoreCase("QUIT")) {
Serial.println("Deletion cancelled");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Cancelled");
delay(2000);
deleteModeActive = false;
currentMode = 0;
showMainMenu();
}
}
if (deleteModeActive && !deleteConfirmed && (millis() - deleteStartTime > 30000)) {
Serial.println("\nTimeout: Returning to main menu");
deleteModeActive = false;
currentMode = 0;
showMainMenu();
}
}
// Data Management Functions
bool isStudentIdUsed(String studentId) {
for (int i = 0; i < userCount; i++) {
if (users[i].studentId == studentId && users[i].active) {
return true;
}
}
return false;
}
bool isProfessorExists() {
for (int i = 0; i < userCount; i++) {
if (users[i].type == "PROFESSOR" && users[i].active) {
return true;
}
}
return false;
}
String getNameByFPId(uint8_t fpId) {
for (int i = 0; i < userCount; i++) {
if (users[i].fpId == fpId && users[i].active) {
return users[i].name;
}
}
return "Unknown";
}
String getStudentIdByFPId(uint8_t fpId) {
for (int i = 0; i < userCount; i++) {
if (users[i].fpId == fpId && users[i].active) {
if (users[i].type == "STUDENT") {
return users[i].studentId;
} else {
return "PROFESSOR";
}
}
}
return "Unknown";
}
String getUserTypeByFPId(uint8_t fpId) {
for (int i = 0; i < userCount; i++) {
if (users[i].fpId == fpId && users[i].active) {
return users[i].type;
}
}
return "UNKNOWN";
}
void findNextAvailableFPId() {
nextAvailableFPId = 1;
bool used[128] = {false};
for (int i = 0; i < userCount; i++) {
if (users[i].active && users[i].fpId > 0 && users[i].fpId < 128) {
used[users[i].fpId] = true;
}
}
for (int i = 1; i < 128; i++) {
if (!used[i]) {
nextAvailableFPId = i;
break;
}
}
}
void saveUserData(String studentId, String name, String type, uint8_t fpId) {
users[userCount].studentId = studentId;
users[userCount].name = name;
users[userCount].type = type;
users[userCount].fpId = fpId;
users[userCount].active = true;
userCount++;
File file = SPIFFS.open("/users.txt", FILE_APPEND);
if (file) {
file.print(studentId);
file.print(",");
file.print(name);
file.print(",");
file.print(type);
file.print(",");
file.println(fpId);
file.close();
}
}
void deleteUserData(String studentId) {
for (int i = 0; i < userCount; i++) {
if (users[i].studentId == studentId) {
users[i].active = false;
break;
}
}
File file = SPIFFS.open("/users.txt", "w");
if (file) {
for (int i = 0; i < userCount; i++) {
if (users[i].active) {
file.print(users[i].studentId);
file.print(",");
file.print(users[i].name);
file.print(",");
file.print(users[i].type);
file.print(",");
file.println(users[i].fpId);
}
}
file.close();
}
}
void loadUserData() {
userCount = 0;
if (SPIFFS.exists("/users.txt")) {
File file = SPIFFS.open("/users.txt", "r");
if (file) {
while (file.available() && userCount < 128) {
String line = file.readStringUntil('\n');
line.trim();
if (line.length() > 0) {
int comma1 = line.indexOf(',');
int comma2 = line.indexOf(',', comma1 + 1);
int comma3 = line.indexOf(',', comma2 + 1);
if (comma1 > 0 && comma2 > 0 && comma3 > 0) {
users[userCount].studentId = line.substring(0, comma1);
users[userCount].name = line.substring(comma1 + 1, comma2);
users[userCount].type = line.substring(comma2 + 1, comma3);
users[userCount].fpId = line.substring(comma3 + 1).toInt();
users[userCount].active = true;
userCount++;
}
}
}
file.close();
}
}
}
void saveAttendanceLog(String studentId, String name, String time, String date) {
if (isAttendanceRecorded(studentId, date)) return;
logs[logCount].studentId = studentId;
logs[logCount].name = name;
logs[logCount].timestamp = time;
logs[logCount].date = date;
logCount++;
File file = SPIFFS.open("/attendance.txt", FILE_APPEND);
if (file) {
file.print(studentId);
file.print(",");
file.print(name);
file.print(",");
file.print(time);
file.print(",");
file.println(date);
file.close();
}
}
bool isAttendanceRecorded(String studentId, String date) {
for (int i = 0; i < logCount; i++) {
if (logs[i].studentId == studentId && logs[i].date == date) {
return true;
}
}
if (SPIFFS.exists("/attendance.txt")) {
File file = SPIFFS.open("/attendance.txt", "r");
if (file) {
while (file.available()) {
String line = file.readStringUntil('\n');
if (line.indexOf(studentId) == 0 && line.indexOf(date) > 0) {
file.close();
return true;
}
}
file.close();
}
}
return false;
}
void loadAttendanceLogs() {
logCount = 0;
if (SPIFFS.exists("/attendance.txt")) {
File file = SPIFFS.open("/attendance.txt", "r");
if (file) {
while (file.available() && logCount < 500) {
String line = file.readStringUntil('\n');
line.trim();
if (line.length() > 0) {
int comma1 = line.indexOf(',');
int comma2 = line.indexOf(',', comma1 + 1);
int comma3 = line.indexOf(',', comma2 + 1);
if (comma1 > 0 && comma2 > 0 && comma3 > 0) {
logs[logCount].studentId = line.substring(0, comma1);
logs[logCount].name = line.substring(comma1 + 1, comma2);
logs[logCount].timestamp = line.substring(comma2 + 1, comma3);
logs[logCount].date = line.substring(comma3 + 1);
logCount++;
}
}
}
file.close();
}
}
}
void loop() {
if (manualTimeSet && Serial.available()) {
String timeInput = Serial.readStringUntil('\n');
timeInput.trim();
setManualTime(timeInput);
return;
}
if (Serial.available() && currentMode != 2 && currentMode != 4 && currentMode != 5) {
String input = Serial.readStringUntil('\n');
input.trim();
lastActivity = millis();
if (currentMode == 0) {
handleMainMenu(input);
} else if (currentMode == 1) {
handleEnrollment(input);
}
}
if (currentMode == 2) {
handleDeleteMode();
} else if (currentMode == 3) {
handleAttendanceMode();
} else if (currentMode == 4) {
handleTimeSetMode();
} else if (currentMode == 5) {
handleClearRecordsMode();
} else if (currentMode == 0) {
updateClockDisplay();
}
// No timeout for attendance mode - only for other modes
if (currentMode != 0 && currentMode != 3 && (millis() - lastActivity > inactivityTimeout)) {
Serial.println("\nTimeout: Returning to main menu");
currentMode = 0;
attendanceActive = false;
professorAuthenticated = false;
deleteModeActive = false;
clearModeActive = false;
timeSetModeActive = false;
showMainMenu();
}
delay(50);
}
Comments
Post a Comment