Khóa cửa thông minh RFID + điều khiển qua app
Trung cấp1/8/2026- Tác giả: IoTlabs Maker

Khóa cửa thông minh RFID + điều khiển qua app

ESP32 + đầu đọc RFID RC522 mở khóa cửa qua thẻ hợp lệ, đồng thời host một API HTTP nội bộ để mở khóa từ xa qua app, dùng chung logic khóa/mở tự động khóa lại sau vài giây.

ESP32RFIDRC522RelaySmart LockKhóa cửa thông minh

Khóa cửa RFID kết hợp 2 cách mở khóa: quẹt thẻ RFID hợp lệ tại chỗ qua module RC522, và gửi lệnh mở khóa từ xa qua một API HTTP nội bộ do chính ESP32 host, mô phỏng luồng "điều khiển qua app".

Hướng dẫn chi tiết

ESP32 + RC522 mở khóa qua thẻ RFID hợp lệ, kèm API HTTP nội bộ để mở khóa từ xa qua app — hướng dẫn đầy đủ 8 mục, sơ đồ đấu nối và code compile pass.

1. Giới thiệu

Khóa cửa RFID kết hợp 2 cách mở khóa: (1) quẹt thẻ/tag RFID hợp lệ tại chỗ qua module RC522, và (2) gửi lệnh mở khóa từ xa qua một API HTTP nội bộ do chính ESP32 host (mô phỏng luồng "điều khiển qua app").

Khi xác thực hợp lệ (thẻ đúng UID hoặc token app đúng), ESP32 kích relay đóng mạch cấp nguồn cho khóa điện từ trong khoảng thời gian cấu hình rồi tự khóa lại — không cần người dùng nhớ tắt tay.

2. Linh kiện cần có

Linh kiện

Số lượng

Ghi chú

ESP32 DevKit V4

1

Board chính, xử lý RFID (SPI) + WiFi + WebServer

RFID RC522 Reader/Writer (mod_rc522_rfid)

1

Đọc UID thẻ/tag 13.56MHz qua SPI, chỉ cấp nguồn 3.3V

Relay Module 1 kênh 5V (mod_relay_1ch)

1

Đóng mạch cấp nguồn cho khóa điện từ khi xác thực hợp lệ

Van điện từ Solenoid 12V (mod_solenoid_valve)

1

Thay thế cho khóa cửa điện từ 12V thật — catalog hiện chưa có mục "khóa cửa điện từ" riêng; cùng đặc tính điện (cuộn dây DC 12V) nên dùng tạm làm minh hoạ tải solenoid

Diode chỉnh lưu 1N4007 (comp_diode_1n4007)

1

Diode flyback mắc song song ngược cực qua cuộn dây khóa/solenoid để bảo vệ relay

3. Sơ đồ kết nối

Chân module

ESP32

RC522 VCC

3V3 (CHỈ 3.3V — không cấp 5V)

RC522 GND

GND

RC522 RST

GPIO27

RC522 SDA/SS

GPIO5

RC522 MOSI

GPIO23

RC522 MISO

GPIO19

RC522 SCK

GPIO18

Relay VCC

5V

Relay GND

GND

Relay IN

GPIO26

Relay NO/COM

+12V nguồn ngoài → Solenoid VCC (khóa điện từ)

Diode 1N4007 CATHODE

Solenoid VCC (+12V)

Diode 1N4007 ANODE

Solenoid GND

Ghi chú quan trọng: RC522 chỉ chịu được 3.3V ở VCC — cấp nhầm 5V sẽ hỏng module. Diode 1N4007 gắn song song ngược cực qua hai đầu cuộn dây khóa/solenoid (cathode về phía dương nguồn) để dập xung điện áp ngược sinh ra khi relay ngắt tải cảm ứng, bảo vệ tiếp điểm relay khỏi hồ quang điện.

4. Code ví dụ #1 — Đọc UID thẻ RFID qua RC522 (SPI)

Đoạn trích minh hoạ hàm đổi UID sang chuỗi hex và vòng lặp đọc thẻ:

String uidToString(MFRC522::Uid uid) {
  String s = "";
  for (byte i = 0; i < uid.size; i++) {
    if (uid.uidByte[i] < 0x10) s += "0";
    s += String(uid.uidByte[i], HEX);
    if (i + 1 < uid.size) s += " ";
  }
  s.toUpperCase();
  return s;
}

// Trong loop():
if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
  String uid = uidToString(rfid.uid);
  Serial.println(uid); // vd "DE AD BE EF"
  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();
}

5. Code ví dụ #2 — Mở khóa qua lệnh app (WebServer HTTP)

Đoạn trích route /unlock — app gửi POST kèm header X-Api-Token đúng token cấu hình để mở khóa từ xa:

void handleUnlock() {
  String token = server.header("X-Api-Token");
  if (token != APP_API_TOKEN) {
    server.send(401, "application/json", "{\"error\":\"invalid token\"}");
    return;
  }
  setLock(true, "app request");
  server.send(200, "application/json", "{\"status\":\"unlocked\"}");
}

// setup(): server.on("/unlock", HTTP_POST, handleUnlock);
//          server.on("/status", HTTP_GET, handleStatus);

6. Code ví dụ #3 — Ứng dụng thực tế đầy đủ — RFID + Relay + WebServer app control

Code đầy đủ khởi động, kiểm tra RC522 đọc được version register lúc boot (degrade "rfid=null" trong log nếu không đọc được), WiFi reconnect có timeout, tự khóa lại sau 4 giây:

/*
  Khóa cửa thông minh RFID + điều khiển qua app
  Board: ESP32 DevKit V4 (board_esp32_devkitc)
  - RFID RC522 (mod_rc522_rfid, SPI, VCC 3.3V CHỈ):
      RST  -> GPIO27
      SDA/SS -> GPIO5
      MOSI -> GPIO23
      MISO -> GPIO19
      SCK  -> GPIO18
      VCC  -> 3V3, GND -> GND
  - Relay 1 kênh (mod_relay_1ch) IN -> GPIO26, VCC -> 5V, GND -> GND
      NO/COM đóng mạch cấp nguồn 12V cho khóa điện từ / van solenoid (mod_solenoid_valve,
      dùng thay thế khóa điện từ 12V thật vì catalog hiện chưa có "khóa cửa điện từ" riêng
      - cùng đặc tính điện: cuộn dây DC 12V, cần diode chống dội flyback).
  - Diode 1N4007 (comp_diode_1n4007) mắc song song ngược cực với cuộn dây khóa để bảo vệ relay
    khỏi điện áp cảm ứng ngược khi ngắt tải.

  Tính năng "điều khiển qua app": ESP32 mở một WebServer HTTP nội bộ (cùng mạng LAN) nhận
  lệnh mở khóa từ xa (POST /unlock), ngoài luồng quét thẻ RFID tại chỗ.
*/

#include <WiFi.h>
#include <WebServer.h>
#include <SPI.h>
#include <MFRC522.h>

// ==== Cấu hình người dùng ====
const char *WIFI_SSID = "YOUR_WIFI_SSID";
const char *WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char *APP_API_TOKEN = "YOUR_SHARED_SECRET_TOKEN"; // app phải gửi kèm token này

// ==== Chân kết nối ====
const int PIN_RC522_RST = 27;
const int PIN_RC522_SS = 5;
const int PIN_RELAY_IN = 26;

const unsigned long UNLOCK_DURATION_MS = 4000; // giữ khóa mở 4s rồi tự khóa lại
const unsigned long WIFI_RETRY_INTERVAL_MS = 5000;
const unsigned long WIFI_CONNECT_TIMEOUT_MS = 10000;
const unsigned long TELEMETRY_INTERVAL_MS = 5000;

// Danh sách UID thẻ được phép (thay bằng UID thật khi triển khai)
const char *ALLOWED_UIDS[] = {"DE AD BE EF", "12 34 56 78"};
const size_t ALLOWED_UIDS_COUNT = sizeof(ALLOWED_UIDS) / sizeof(ALLOWED_UIDS[0]);

MFRC522 rfid(PIN_RC522_SS, PIN_RC522_RST);
WebServer server(80);

unsigned long lastWifiAttemptMs = 0;
unsigned long lastTelemetryMs = 0;
unsigned long unlockUntilMs = 0;
bool locked = true;
bool rfidReady = false;

void setLock(bool unlock, const char *reason) {
  digitalWrite(PIN_RELAY_IN, unlock ? LOW : HIGH); // active-low relay
  locked = !unlock;
  if (unlock) {
    unlockUntilMs = millis() + UNLOCK_DURATION_MS;
  }
  Serial.printf("[Lock] %s (ly do: %s)\n", unlock ? "MO KHOA" : "KHOA LAI", reason);
}

String uidToString(MFRC522::Uid uid) {
  String s = "";
  for (byte i = 0; i < uid.size; i++) {
    if (uid.uidByte[i] < 0x10) s += "0";
    s += String(uid.uidByte[i], HEX);
    if (i + 1 < uid.size) s += " ";
  }
  s.toUpperCase();
  return s;
}

bool isAllowedUid(const String &uid) {
  for (size_t i = 0; i < ALLOWED_UIDS_COUNT; i++) {
    if (uid.equalsIgnoreCase(ALLOWED_UIDS[i])) return true;
  }
  return false;
}

void handleUnlock() {
  String token = server.header("X-Api-Token");
  if (token != APP_API_TOKEN) {
    server.send(401, "application/json", "{\"error\":\"invalid token\"}");
    return;
  }
  setLock(true, "app request");
  server.send(200, "application/json", "{\"status\":\"unlocked\"}");
}

void handleStatus() {
  String json = String("{\"locked\":") + (locked ? "true" : "false") +
                ",\"rfidReady\":" + (rfidReady ? "true" : "false") + "}";
  server.send(200, "application/json", json);
}

void connectWiFiNonBlocking() {
  if (WiFi.status() == WL_CONNECTED) return;
  unsigned long now = millis();
  if (now - lastWifiAttemptMs < WIFI_RETRY_INTERVAL_MS) return;
  lastWifiAttemptMs = now;

  Serial.printf("[WiFi] Dang ket noi toi SSID \"%s\" ...\n", WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - start < WIFI_CONNECT_TIMEOUT_MS) {
    delay(200);
    Serial.print(".");
  }
  Serial.println();

  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("[WiFi] Da ket noi. IP: %s\n", WiFi.localIP().toString().c_str());
    server.begin();
    Serial.println("[HTTP] WebServer san sang: POST /unlock, GET /status");
  } else {
    Serial.println("[WiFi] Ket noi that bai, se thu lai sau.");
  }
}

void setup() {
  Serial.begin(115200);
  delay(300);
  Serial.println();
  Serial.println("========================================");
  Serial.println(" IoTLabs Maker - Khoa Cua RFID + App");
  Serial.println(" Board: ESP32 DevKit V4");
  Serial.println("========================================");

  pinMode(PIN_RELAY_IN, OUTPUT);
  setLock(false, "khoi dong");

  SPI.begin(); // SCK=GPIO18, MISO=GPIO19, MOSI=GPIO23 (mac dinh VSPI tren ESP32 DevKit)
  rfid.PCD_Init();
  byte version = rfid.PCD_ReadRegister(MFRC522::VersionReg);
  rfidReady = (version != 0x00 && version != 0xFF);
  if (rfidReady) {
    Serial.printf("[RC522] Khoi tao OK (version reg=0x%02X)\n", version);
  } else {
    Serial.println("[RC522] KHONG doc duoc module - kiem tra day noi SPI/nguon 3.3V.");
  }

  server.on("/unlock", HTTP_POST, handleUnlock);
  server.on("/status", HTTP_GET, handleStatus);

  connectWiFiNonBlocking();
}

void loop() {
  connectWiFiNonBlocking();
  if (WiFi.status() == WL_CONNECTED) {
    server.handleClient();
  }

  if (!locked && millis() > unlockUntilMs) {
    setLock(false, "het thoi gian giu khoa");
  }

  if (rfidReady && rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
    String uid = uidToString(rfid.uid);
    bool allowed = isAllowedUid(uid);
    Serial.printf("[RFID] The quet UID=%s -> %s\n", uid.c_str(), allowed ? "HOP LE" : "TU CHOI");
    if (allowed) {
      setLock(true, "the RFID hop le");
    }
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
  }

  unsigned long now = millis();
  if (now - lastTelemetryMs >= TELEMETRY_INTERVAL_MS) {
    lastTelemetryMs = now;
    Serial.printf("[Telemetry] locked=%s rfid=%s wifi=%s\n",
                  locked ? "true" : "false",
                  rfidReady ? "ready" : "null",
                  WiFi.status() == WL_CONNECTED ? "connected" : "disconnected");
  }

  delay(50);
}

7. Lỗi thường gặp

Lỗi

Nguyên nhân

Cách sửa

Serial in "RC522 KHONG doc duoc module"

Sai dây SPI, hoặc cấp nhầm 5V vào VCC làm hỏng module

Kiểm tra lại 4 dây SPI (MOSI/MISO/SCK/SS) đúng chân, đo VCC phải đúng 3.3V

Quẹt thẻ đúng nhưng không mở khóa

UID thẻ thực tế chưa có trong mảng ALLOWED_UIDS, hoặc RC522 bị treo do nhiễu SPI dây quá dài

In UID ra Serial khi quẹt để lấy đúng UID thật rồi cập nhật vào mảng; rút ngắn dây SPI dưới 20cm

App gọi /unlock trả về 401

Token gửi từ app không khớp APP_API_TOKEN cấu hình trên ESP32

Đồng bộ lại giá trị token giữa app và firmware, tránh hard-code token thật trong code mẫu công khai

Relay kêu tách liên tục ngẫu nhiên dù không quẹt thẻ

Thiếu diode flyback qua cuộn dây solenoid gây nhiễu điện áp ngược ảnh hưởng mạch điều khiển

Lắp đúng diode 1N4007 song song ngược cực qua cuộn dây như sơ đồ mục 3

Lưu ý an toàn

  • Mạch điều khiển khóa cửa nằm trong nhóm an toàn quan trọng: relay đóng/ngắt tải cảm ứng (solenoid) bắt buộc phải có diode flyback để tránh xung điện áp ngược làm hỏng relay hoặc gây nhiễu MCU.

  • Với khóa cửa thật, nên có thêm cơ chế mở khóa cơ khí dự phòng (chìa khóa vật lý) để tránh bị khóa cứng khi mất điện/lỗi firmware — đây là khuyến nghị an toàn bắt buộc cho mọi khóa điện tử lắp thật.

8. Tổng kết

Dự án minh hoạ 2 luồng xác thực song song cho một khóa cửa IoT: RFID tại chỗ và API app từ xa, cùng chia sẻ một hàm setLock() trung tâm để tránh trạng thái xung đột.

Có thể mở rộng: lưu UID được phép trên server thay vì hard-code, log lịch sử mở khóa, hoặc thêm xác thực 2 lớp (RFID + PIN) cho khu vực nhạy cảm hơn.