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

Popular posts from this blog

ESP32 and I2C LCD Integration

RTC and LCD - how the code works

Planning