Attendance tracking is an essential task in schools, organizations, and workplaces, yet traditional methods such as paper-based sign-in sheets or manual entry are time-consuming and prone to errors. With the advancement of Internet of Things (IoT) technology, automated attendance systems have become more efficient, accurate, and easy to manage.

This project presents a simple attendance system using RFID cards, an ESP32 microcontroller, and Google Sheets as a cloud-based database. Each user is assigned a unique RFID card, and when the card is scanned, the ESP32 reads the card’s ID and sends the attendance data to Google Sheets via Wi-Fi. This allows attendance records to be stored automatically, securely, and accessed in real time from anywhere.

The system aims to demonstrate a low-cost, reliable, and scalable solution for attendance management while introducing basic concepts of embedded systems, wireless communication, and cloud integration.

Hardware Components

Connections guide:

image.png

Code:

In attendance.ino :

#include <SPI.h>
#include <MFRC522.h>
#define SS_PIN 16
#define RST_PIN 17
MFRC522 rfid(SS_PIN, RST_PIN);
String uidString;

#include "WiFi.h"
#include <HTTPClient.h>
const char* ssid = "DTM E-SMART";  //--> Your wifi name
const char* password = "0919890938";
// Google script Web_App_URL.
String Web_App_URL = "Replace with URL";
#define On_Board_LED_PIN 2
#define MAX_STUDENTS 10 // Max students
struct Student {
  String id;
  char code[10];
  char name[30];
};
Student students[MAX_STUDENTS];
int studentCount = 0;
int runMode=2;
const int btnIO = 15;
bool btnIOState = HIGH;
unsigned long timeDelay=millis();
unsigned long timeDelay2=millis();
bool InOutState=0; //0: vào, 1:ra
const int ledIO = 4;
const int buzzer = 5;

void setup() {
  Serial.begin(115200);
  pinMode(buzzer,OUTPUT);
  digitalWrite(buzzer,LOW);
  pinMode(btnIO,INPUT_PULLUP);
  pinMode(ledIO,OUTPUT);
  pinMode(On_Board_LED_PIN,OUTPUT);
  Serial.println();
  Serial.println("-------------");
  Serial.println("WIFI mode : STA");
  Serial.println("-------------");
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  if(!readDataSheet()) Serial.println("Can't read data from google sheet!");

  SPI.begin(); // Init SPI bus
  rfid.PCD_Init(); // Init MFRC522
}

void loop() {
  if(millis()-timeDelay2>500){
    readUID();
    timeDelay2=millis();
  }
  if(digitalRead(btnIO)==LOW){
    if(btnIOState==HIGH){
      if(millis()-timeDelay>500){
        //Thực hiện lệnh
        InOutState = !InOutState;
        digitalWrite(ledIO,InOutState);
        timeDelay=millis();
      }
      btnIOState=LOW;
    }
  }else{
    btnIOState=HIGH;
  }
}
void beep(int n, int d){
  for(int i=0;i<n;i++){
    digitalWrite(buzzer,HIGH);
    delay(d);
    digitalWrite(buzzer,LOW);
    delay(d);
  }
}
void readUID(){
  // Prepare key - all keys are set to FFFFFFFFFFFFh at chip delivery from the factory.
  MFRC522::MIFARE_Key key;
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
  // Look for new cards
  if ( ! rfid.PICC_IsNewCardPresent()) {
    return;
  }

  // Select one of the cards
  if ( ! rfid.PICC_ReadCardSerial()) {
    return;
  }
  // Now a card is selected. The UID and SAK is in mfrc522.uid.

  uidString="";
  for (byte i = 0; i < rfid.uid.size; i++) {
    uidString.concat(String(rfid.uid.uidByte[i] < 0x10 ? "0" : ""));
    uidString.concat(String(rfid.uid.uidByte[i], HEX));
  }
  uidString.toUpperCase();
  Serial.println("Card UID: "+uidString);
  beep(1,200);
  if(runMode==1)  writeUIDSheet();
  else if(runMode==2) writeLogSheet();
  // Dump PICC type
  byte piccType = rfid.PICC_GetType(rfid.uid.sak);
  //    Serial.print("PICC type: ");
  //Serial.println(mfrc522.PICC_GetTypeName(piccType));
  if (        piccType != MFRC522::PICC_TYPE_MIFARE_MINI
              &&        piccType != MFRC522::PICC_TYPE_MIFARE_1K
              &&        piccType != MFRC522::PICC_TYPE_MIFARE_4K) {
    //Serial.println("This sample only works with MIFARE Classic cards.");
    return;
  }
}
bool readDataSheet(){
  if (WiFi.status() == WL_CONNECTED) {
    digitalWrite(On_Board_LED_PIN, HIGH);

    // Create a URL for reading or getting data from Google Sheets.
    String Read_Data_URL = Web_App_URL + "?sts=read";

    Serial.println();
    Serial.println("-------------");
    Serial.println("Read data from Google Spreadsheet...");
    Serial.print("URL : ");
    Serial.println(Read_Data_URL);

    //::::::::::::::::::The process of reading or getting data from Google Sheets.
      // Initialize HTTPClient as "http".
      HTTPClient http;

      // HTTP GET Request.
      http.begin(Read_Data_URL.c_str());
      http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);

      // Gets the HTTP status code.
      int httpCode = http.GET(); 
      Serial.print("HTTP Status Code : ");
      Serial.println(httpCode);
  
      // Getting response from google sheet.
      String payload;
      studentCount=0;
      if (httpCode > 0) {
        payload = http.getString();
        Serial.println("Payload : " + payload); 

         // split
          char charArray[payload.length() + 1];
          payload.toCharArray(charArray, payload.length() + 1);
          // Count
          int numberOfElements = countElements(charArray, ',');
          Serial.println("Number of elements: "+String(numberOfElements));
          char *token = strtok(charArray, ",");
          while (token != NULL && studentCount < numberOfElements/3) {
            students[studentCount].id = atoi(token); // ID change
            token = strtok(NULL, ",");
            strcpy(students[studentCount].code, token); // Copy ID
            token = strtok(NULL, ",");
            strcpy(students[studentCount].name, token); // Copy name
            
            studentCount++;
            token = strtok(NULL, ",");
          }
          
          // print student list
          for (int i = 0; i < studentCount; i++) {
            Serial.print("ID: ");
            Serial.println(students[i].id);
            Serial.print("Code: ");
            Serial.println(students[i].code);
            Serial.print("Name: ");
            Serial.println(students[i].name);
          }
      }
  
      http.end();
    //::::::::::::::::::
    
    digitalWrite(On_Board_LED_PIN, LOW);
    Serial.println("-------------");
    if(studentCount>0) return true;
    else return false;
  }
}
void writeUIDSheet(){
  String Send_Data_URL = Web_App_URL + "?sts=writeuid";
  Send_Data_URL += "&uid=" + uidString;
  Serial.println();
  Serial.println("-------------");
  Serial.println("Send data to Google Spreadsheet...");
  Serial.print("URL : ");
  Serial.println(Send_Data_URL);
  // Initialize HTTPClient as "http".
  HTTPClient http;

  // HTTP GET Request.
  http.begin(Send_Data_URL.c_str());
  http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);

  // Gets the HTTP status code.
  int httpCode = http.GET(); 
  Serial.print("HTTP Status Code : ");
  Serial.println(httpCode);

  // Getting response from google sheets.
  String payload;
  if (httpCode > 0) {
    payload = http.getString();
    Serial.println("Payload : " + payload);    
  }
  
  http.end();
}
void writeLogSheet(){
  char charArray[uidString.length() + 1];
  uidString.toCharArray(charArray, uidString.length() + 1);
  char* studentName = getStudentNameById(charArray);
  if (studentName != nullptr) {
    Serial.print("Student name with ID ");
    Serial.print(uidString);
    Serial.print(" is: ");
    Serial.println(studentName);

    String Send_Data_URL = Web_App_URL + "?sts=writelog";
    Send_Data_URL += "&uid=" + uidString;
    Send_Data_URL += "&name=" + urlencode(String(studentName));
    if(InOutState==0){
      Send_Data_URL += "&inout="+urlencode("VÀO");
    }else{
      Send_Data_URL += "&inout="+urlencode("RA");
    }

    Serial.println();
    Serial.println("-------------");
    Serial.println("Send data to Google Spreadsheet...");
    Serial.print("URL : ");
    Serial.println(Send_Data_URL);
    // Initialize HTTPClient as "http".
    HTTPClient http;

    // HTTP GET Request.
    http.begin(Send_Data_URL.c_str());
    http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);

    // Gets the HTTP status code.
    int httpCode = http.GET(); 
    Serial.print("HTTP Status Code : ");
    Serial.println(httpCode);

    // Getting response from google sheets.
    String payload;
    if (httpCode > 0) {
      payload = http.getString();
      Serial.println("Payload : " + payload);    
    }
    
    http.end();
  } else {
    Serial.print("No student with that ID ");
    Serial.println(uidString);
    beep(3,500);
  }
}
String urlencode(String str) {
  String encodedString = "";
  char c;
  char code0;
  char code1;
  char code2;
  for (int i = 0; i < str.length(); i++) {
    c = str.charAt(i);
    if (c == ' ') {
      encodedString += '+';
    } else if (isalnum(c)) {
      encodedString += c;
    } else {
      code1 = (c & 0xf) + '0';
      if ((c & 0xf) > 9) {
        code1 = (c & 0xf) - 10 + 'A';
      }
      c = (c >> 4) & 0xf;
      code0 = c + '0';
      if (c > 9) {
        code0 = c - 10 + 'A';
      }
      code2 = '\\0';
      encodedString += '%';
      encodedString += code0;
      encodedString += code1;
    }
    yield();
  }
  return encodedString;
}
char* getStudentNameById(char* uid) {
  for (int i = 0; i < studentCount; i++) {
    if (strcmp(students[i].code, uid) == 0) {
      return students[i].name;
    }
  }
  return nullptr;
}
int countElements(const char* data, char delimiter) {
  
  char dataCopy[strlen(data) + 1];
  strcpy(dataCopy, data);
  
  int count = 0;
  char* token = strtok(dataCopy, &delimiter);
  while (token != NULL) {
    count++;
    token = strtok(NULL, &delimiter);
  }
  return count;
}

You should also downloaded MFRC522 library on Arduino IDE

Go to Google Sheet and create new file, with two tabs within in it:

tab1: Students information