Objetivo y caso de uso
Qué construirás: Un terminal de control de acceso NFC avanzado utilizando ESP32 y PN532, que muestra información en una pantalla E-Paper de 2.9″.
Para qué sirve
- Control de acceso en edificios mediante identificación NFC.
- Gestión de listas de control de acceso (ACLs) para diferentes usuarios.
- Visualización de información de acceso en tiempo real en la pantalla E-Paper.
- Integración con sistemas de notificación mediante MQTT para alertas de acceso.
Resultado esperado
- Tiempo de respuesta de acceso inferior a 500 ms.
- Capacidad para gestionar hasta 1000 usuarios en la base de datos de ACLs.
- Latencia de comunicación entre ESP32 y PN532 menor a 50 ms.
- Actualización de la pantalla E-Paper cada vez que se registra un acceso, con un tiempo de refresco de 1 segundo.
Público objetivo: Desarrolladores avanzados; Nivel: Avanzado
Arquitectura/flujo: ESP32 <-> PN532 <-> E-Paper <-> MQTT
Nivel: Avanzado
Prerrequisitos
Sistema operativo y utilidades
- Windows 11 23H2, macOS 14.5 (Sonoma) o Ubuntu 22.04.5 LTS (x86_64).
- Python 3.11.6 instalado y en PATH (recomendado usar pyenv/pipx en Linux/macOS).
- Cable micro‑USB de datos (no solo carga).
Toolchain concreta (versiones exactas)
- PlatformIO Core 6.1.13
- PlatformIO Platform: espressif32 @ 6.6.0
- Framework Arduino-ESP32: 2.0.14
- Toolchain GCC Xtensa (ESP32): 11.2.0 (xtensa-esp32-elf-gcc 11.2.0, binutils 2.37)
- esptool.py 4.6
- OpenOCD (para depuración opcional): 0.12.0-esp32-20230921
Comando de verificación de versiones propuestas (tras instalar PlatformIO):
– pio –version → PlatformIO Core, versión 6.1.13
– pio pkg update; pio pkg list (dentro del proyecto) → mostrará las versiones de platform/framework/toolchain usadas.
– python –version → Python 3.11.6
Controladores USB (según tu placa)
- NodeMCU-32S suele integrar CP210x (Silicon Labs). Instalar:
- Windows: “CP210x Universal Windows Driver” (v11 o superior).
- macOS: no suele requerirse desde 10.15+, pero es recomendable instalar el paquete de Silicon Labs si no aparece el puerto.
- Linux: kernel reciente incluye cp210x. Verifica permisos de /dev/ttyUSBx o /dev/tty.SLAB_USBtoUART.
- Algunas variantes traen CH34x:
- Windows: CH341SER.EXE (WCH).
- Linux/macOS: soportado en kernel modernos, pero puede requerir permisos/udev.
Notas de Linux (opcional):
– Agrega una regla udev para acceso sin sudo:
– Crear /etc/udev/rules.d/99-esp32.rules con:
– SUBSYSTEM==»tty», ATTRS{idVendor}==»10c4″, ATTRS{idProduct}==»ea60″, MODE:=»0666″
– sudo udevadm control –reload-rules && sudo udevadm trigger
Materiales
- ESP32 NodeMCU-32S (modelo exacto de placa “NodeMCU-32S”).
- Waveshare 2.9″ E‑Paper (controlador SSD1680) + cableado Dupont hembra-hembra.
- Módulo PN532 NFC (Waveshare o Adafruit/Genérico) con interfaz SPI habilitada.
- Tarjetas y/o llaveros NFC tipo A (MIFARE Classic/Ultralight/NTAG).
- Fuente de alimentación: USB 5V (PC o cargador estable de 1A); el regulador onboard del NodeMCU-32S alimentará 3V3 para PN532 y E‑Paper.
- Opcional: protoboard, resistencias pull-up si tu PN532 lo requiere para IRQ (no usaremos IRQ en este caso), soportes/adhesivos.
Comentarios de consumo:
– Waveshare 2.9″ e‑paper: ~26 mA durante refresco activo; ~<1 mA reposo.
– PN532: 50–80 mA durante poll activo.
– NodeMCU-32S puede alimentar ambos desde 3V3 onboard con margen si la entrada 5V es estable.
Preparación y conexión
Este caso práctico usa un bus SPI compartido (VSPI del ESP32) para E‑Paper y PN532, cada uno con su propio pin CS. El SSD1680 no requiere MISO. El PN532 sí usa MISO.
- Bus SPI (VSPI) del ESP32 NodeMCU-32S:
- SCLK → GPIO18
- MOSI → GPIO23
- MISO → GPIO19 (solo PN532)
- Pines auxiliares E‑Paper:
- CS, DC, RST, BUSY
- Pines PN532:
- SS (CS)
- (Opcional) RSTO/IRQ: no usado en este ejemplo (polling).
Tabla de mapeo de pines y alimentación:
| Módulo | Señal/Pin | ESP32 NodeMCU-32S | Notas |
|---|---|---|---|
| Waveshare 2.9″ E‑Paper (SSD1680) | VCC | 3V3 | Alimentación 3.3V |
| GND | GND | Tierra común | |
| DIN (MOSI) | GPIO23 | Datos SPI | |
| CLK (SCLK) | GPIO18 | Reloj SPI | |
| CS | GPIO5 | Chip Select E‑Paper | |
| DC (Data/Command) | GPIO17 | Línea DC | |
| RST | GPIO16 | Reset de panel | |
| BUSY | GPIO4 | Estado ocupado del panel | |
| PN532 NFC (SPI) | VCC | 3V3 | Alimentación 3.3V (no usar 5V en lógica) |
| GND | GND | Tierra común | |
| SCK | GPIO18 | Comparte bus SPI | |
| MOSI | GPIO23 | Comparte bus SPI | |
| MISO | GPIO19 | Entrada MISO para PN532 | |
| SS (CS) | GPIO15 | Chip Select PN532 | |
| LED onboard (opcional) | LED | GPIO2 | Indicador de acceso (on: concedido; parpadeo: denegado) |
Consejos:
– Usa cables cortos y firmes. Evita falsos contactos en BUSY y DC del E‑Paper, que provocan bloqueos.
– No cruces 5V con señales lógicas. Todo va a 3.3V.
– Asegura masa común entre los módulos y el ESP32.
Código completo (Arduino/ESP32 con PlatformIO) y explicación
A continuación se muestra un proyecto completo con:
– Whitelist de UIDs basada en SHA‑256 (no se guardan UIDs en claro).
– NTP para hora real vía WiFi.
– Registro de eventos en LittleFS (CSV).
– Renderizado en E‑Paper con GxEPD2 (SSD1680).
– Lógica de control de acceso (éxito/denegado) y mitigación de ghosting (refresco completo periódico).
platformio.ini
Bloquea versiones de plataforma, framework y librerías.
; platformio.ini
[env:nodemcu-32s]
platform = espressif32 @ 6.6.0
board = nodemcu-32s
framework = arduino
; Toolchain y ajustes de carga/monitor
upload_speed = 921600
monitor_speed = 115200
monitor_filters = direct, time
board_build.flash_mode = dio
; Versiones de librerías
lib_deps =
zinggjm/GxEPD2 @ 1.5.8
adafruit/Adafruit PN532 @ 1.3.3
adafruit/Adafruit BusIO @ 1.14.1
adafruit/Adafruit GFX Library @ 1.11.9
; Opcional: reducir warnings verbosos
build_flags =
-DCORE_DEBUG_LEVEL=0
; Asegurar framework Arduino-ESP32 versión 2.0.14
platform_packages =
framework-arduinoespressif32@~3.20014.0
toolchain-xtensa-esp32@~11.2.0
tool-esptoolpy@~1.40500.0
Notas:
– framework-arduinoespressif32@3.20014.0 corresponde a Arduino-ESP32 2.0.14 en nomenclatura PlatformIO.
– toolchain-xtensa-esp32 ~11.2.0 alinea con gcc 11.2.0.
src/main.cpp
#include <Arduino.h>
#include <SPI.h>
#include <WiFi.h>
#include <esp_sntp.h>
#include <FS.h>
#include <LittleFS.h>
// E-Paper (SSD1680) con GxEPD2
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeMonoBold9pt7b.h>
// PN532 (SPI)
#include <Adafruit_PN532.h>
// ===== Configuración WiFi/NTP =====
static const char* WIFI_SSID = "TU_SSID";
static const char* WIFI_PASSWORD = "TU_PASSWORD";
static const char* NTP_SERVER = "pool.ntp.org";
static const long GMT_OFFSET = 0; // ajustar según zona horaria
static const int DST_OFFSET = 0; // horario de verano si aplica
// ===== Pines (ESP32 NodeMCU-32S) =====
static const int PIN_EPD_CS = 5;
static const int PIN_EPD_DC = 17;
static const int PIN_EPD_RST = 16;
static const int PIN_EPD_BUSY = 4;
static const int PIN_PN532_SS = 15;
static const int PIN_LED = 2; // LED onboard
// ===== Objetos globales =====
GxEPD2_BW<GxEPD2_290, GxEPD2_290::HEIGHT> display(GxEPD2_290(PIN_EPD_CS, PIN_EPD_DC, PIN_EPD_RST, PIN_EPD_BUSY));
Adafruit_PN532 pn532(PIN_PN532_SS);
// ===== Whitelist (hash SHA-256 hex de UIDs) =====
// Para generar hashes, ver script Python más abajo o función utilitaria.
static const char* WHITELIST_SHA256[] = {
// Ejemplos (reemplaza con los tuyos):
// "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
// "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
};
static const size_t WHITELIST_COUNT = sizeof(WHITELIST_SHA256)/sizeof(WHITELIST_SHA256[0]);
// ===== Utilidades =====
String bytesToHex(const uint8_t* data, size_t len) {
static const char* hex = "0123456789abcdef";
String out; out.reserve(len * 2);
for (size_t i = 0; i < len; ++i) {
out += hex[(data[i] >> 4) & 0x0F];
out += hex[data[i] & 0x0F];
}
return out;
}
// SHA-256 usando mbedTLS del core ESP32
#include "mbedtls/md.h"
bool sha256_hex(const uint8_t* data, size_t len, char out_hex[65]) {
mbedtls_md_context_t ctx;
const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
if (!info) return false;
mbedtls_md_init(&ctx);
if (mbedtls_md_setup(&ctx, info, 0) != 0) { mbedtls_md_free(&ctx); return false; }
if (mbedtls_md_starts(&ctx) != 0) { mbedtls_md_free(&ctx); return false; }
if (mbedtls_md_update(&ctx, data, len) != 0) { mbedtls_md_free(&ctx); return false; }
uint8_t hash[32];
if (mbedtls_md_finish(&ctx, hash) != 0) { mbedtls_md_free(&ctx); return false; }
mbedtls_md_free(&ctx);
static const char* hex = "0123456789abcdef";
for (int i = 0; i < 32; ++i) {
out_hex[2*i] = hex[(hash[i] >> 4) & 0xF];
out_hex[2*i+1] = hex[hash[i] & 0xF];
}
out_hex[64] = '\0';
return true;
}
bool inWhitelist(const char* sha256_hex) {
for (size_t i = 0; i < WHITELIST_COUNT; ++i) {
if (strcasecmp(sha256_hex, WHITELIST_SHA256[i]) == 0) return true;
}
return false;
}
// Tiempo legible
String nowString() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo, 3000)) return String("1970-01-01 00:00:00");
char buf[32];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(buf);
}
// Registro en LittleFS
bool appendLog(const String& line) {
if (!LittleFS.begin(true)) return false;
File f = LittleFS.open("/access_log.csv", FILE_APPEND);
if (!f) return false;
bool ok = f.print(line);
f.close();
return ok;
}
// Renderizado en E‑Paper
void drawAccessScreen(bool granted, const String& uid_hex, const String& timestamp, bool partial = false) {
if (partial) {
display.setPartialWindow(0, 0, display.width(), display.height());
} else {
display.setFullWindow();
}
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.setRotation(1);
display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeMonoBold12pt7b);
int16_t x = 6, y = 26;
display.setCursor(x, y);
display.print("nfc-epaper-access-control");
display.setFont(&FreeMonoBold9pt7b);
y += 28;
display.setCursor(x, y);
display.print("UID: ");
display.print(uid_hex);
y += 24;
display.setCursor(x, y);
display.print("Time: ");
display.print(timestamp);
y += 30;
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(x, y);
if (granted) {
display.print("ACCESS GRANTED");
} else {
display.print("ACCESS DENIED ");
}
// Pie de página
display.setFont(&FreeMonoBold9pt7b);
y += 28;
display.setCursor(x, y);
display.print("Panel: Waveshare 2.9\" (SSD1680)");
} while (display.nextPage());
}
// Conectividad WiFi + NTP
void initWiFiNTP() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("[WiFi] Conectando a "); Serial.println(WIFI_SSID);
unsigned long t0 = millis();
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
if (millis() - t0 > 15000) break;
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.print("[WiFi] OK. IP: "); Serial.println(WiFi.localIP());
configTzTime("UTC0", NTP_SERVER); // se puede ajustar con TZ
// Espera bloqueo NTP
struct tm timeinfo;
for (int i = 0; i < 20; ++i) {
if (getLocalTime(&timeinfo, 500)) break;
delay(500);
}
Serial.println("[NTP] Sincronizado (si red disponible).");
} else {
Serial.println("[WiFi] No conectado. Continuando sin NTP.");
}
}
// Inicialización de periféricos
void initEPaper() {
display.init(115200); // velocidad SPI para E‑Paper
display.setRotation(1);
// Primera pantalla
drawAccessScreen(false, "----", nowString(), false);
}
void initPN532() {
pn532.begin();
uint32_t versiondata = pn532.getFirmwareVersion();
if (!versiondata) {
Serial.println("[PN532] No se detecto el PN532. Verifica cableado y SS.");
} else {
Serial.print("[PN532] Chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
Serial.print("[PN532] Firmware: "); Serial.print((versiondata>>16) & 0xFF, DEC);
Serial.print("."); Serial.println((versiondata>>8) & 0xFF, DEC);
pn532.SAMConfig(); // modo normal, permite lectura pasiva
}
}
void blinkDenied() {
for (int i = 0; i < 3; ++i) {
digitalWrite(PIN_LED, HIGH); delay(120);
digitalWrite(PIN_LED, LOW); delay(120);
}
}
void solidGranted() {
digitalWrite(PIN_LED, HIGH); delay(800);
digitalWrite(PIN_LED, LOW);
}
void setup() {
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, LOW);
Serial.begin(115200);
delay(200);
Serial.println("\n[nfc-epaper-access-control] Inicio");
initWiFiNTP();
if (!LittleFS.begin(true)) {
Serial.println("[FS] LittleFS montado (formateado en primera vez si era necesario).");
}
initEPaper();
initPN532();
Serial.println("[Sistema] Listo. Acerca credencial NFC tipo A al PN532.");
}
void loop() {
static uint32_t lastFullRefresh = 0;
static int opCount = 0;
uint8_t uid[8];
uint8_t uidLength = 0;
// Lee una tarjeta (timeout corto para mantener fluidez)
bool success = pn532.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 50);
if (!success) {
// Refresco completo cada ~60s para mitigar ghosting
if (millis() - lastFullRefresh > 60000) {
drawAccessScreen(false, "----", nowString(), false);
lastFullRefresh = millis();
}
delay(10);
return;
}
String uid_hex = bytesToHex(uid, uidLength);
char sha_hex[65];
if (!sha256_hex(uid, uidLength, sha_hex)) {
Serial.println("[Crypto] Error SHA-256.");
blinkDenied();
return;
}
bool granted = inWhitelist(sha_hex);
String ts = nowString();
// Render parcial para respuesta rápida
drawAccessScreen(granted, uid_hex, ts, true);
if (granted) solidGranted(); else blinkDenied();
// Log CSV: timestamp,uid_hex,sha256_hex,granted
String line = ts + "," + uid_hex + "," + String(sha_hex) + "," + (granted ? "1" : "0") + "\n";
if (!appendLog(line)) {
Serial.println("[FS] Error escribiendo /access_log.csv");
}
Serial.print("[NFC] UID="); Serial.print(uid_hex);
Serial.print(" SHA256="); Serial.print(sha_hex);
Serial.print(" Access="); Serial.println(granted ? "GRANTED" : "DENIED");
opCount++;
// Forzar refresco completo cada 10 operaciones para borrar ghosting
if (opCount % 10 == 0) {
drawAccessScreen(granted, uid_hex, ts, false);
lastFullRefresh = millis();
}
delay(600); // pequeña pausa para evitar múltiples lecturas idénticas instantáneas
}
Explicación breve de partes clave:
– Whitelist mediante SHA‑256: evita exponer UIDs en claro en firmware/FS; puedes generar los hashes desde un PC y pegarlos en WHITELIST_SHA256.
– PN532 por SPI: usa el bus VSPI compartido con el E‑Paper; cada dispositivo tiene su CS (GPIO5 para E‑Paper y GPIO15 para PN532).
– E‑Paper con GxEPD2 (SSD1680): renderiza texto con fuentes GFX. Se usan actualizaciones parciales para respuesta fluida y periódicas completas para eliminar ghosting.
– NTP via WiFi: se sincroniza hora al inicio si hay red; se usa en logs y en pantalla.
– LittleFS: almacena un CSV con los registros de accesos.
– LED onboard: feedback simple de acceso concedido/denegado.
Script auxiliar (opcional) para generar SHA‑256 de UIDs
Úsalo en tu PC para convertir UIDs (en hex) a hash SHA‑256 y pegarlos en WHITELIST_SHA256.
# uid2sha256.py
import sys, binascii, hashlib
def main():
if len(sys.argv) < 2:
print("Uso: python uid2sha256.py <uid_hex> [<uid_hex> ...]")
print("Ejemplo: python uid2sha256.py 04a224b9c13280")
sys.exit(1)
for uid_hex in sys.argv[1:]:
uid_hex = uid_hex.lower().strip()
try:
uid_bytes = binascii.unhexlify(uid_hex)
except Exception as e:
print(f"UID inválido: {uid_hex} ({e})")
continue
sha = hashlib.sha256(uid_bytes).hexdigest()
print(f"UID={uid_hex} SHA256={sha}")
if __name__ == "__main__":
main()
Ejemplo:
– python uid2sha256.py 04a224b9c13280
– Copia el sha256 resultante a WHITELIST_SHA256 en main.cpp.
Compilación / flash / ejecución
Usaremos PlatformIO en modo CLI para reproducibilidad total.
1) Crear proyecto
– mkdir nfc-epaper-access-control
– cd nfc-epaper-access-control
– pio project init –board nodemcu-32s
2) Sustituir/crear archivos
– Copia el contenido de platformio.ini (arriba) a ./platformio.ini
– Crea ./src/main.cpp con el código mostrado
3) Instalar dependencias y compilar
– pio run
4) Enumerar puertos serie
– pio device list
– Windows: COM3/COMx (CP210x/CH340)
– Linux: /dev/ttyUSB0 (CP210x) o /dev/ttyACM0
– macOS: /dev/tty.SLAB_USBtoUART
5) Grabar firmware
– pio run -t upload –upload-port
– Ejemplo Windows: pio run -t upload –upload-port COM5
– Ejemplo Linux/macOS: pio run -t upload –upload-port /dev/ttyUSB0
6) Abrir monitor serie
– pio device monitor -b 115200 –port
7) Verificación de paquetes (opcional)
– pio pkg list
– pio pkg show framework-arduinoespressif32
– pio pkg show platformio/toolchain-xtensa-esp32
Notas:
– Si “Failed to connect to ESP32”, mantén pulsado BOOT y suéltalo cuando empiece el upload (o pulsa EN/RESET tras iniciar el proceso).
– Asegúrate de haber editado WIFI_SSID y WIFI_PASSWORD en main.cpp (puedes dejarlo sin red; el proyecto funciona igual y solo no tendrá hora real).
Validación paso a paso
1) Alimentación y conexión:
– Conecta el NodeMCU-32S al PC.
– Verifica en la tabla de pines que E‑Paper y PN532 están conectados a 3V3 y GND correctamente, y los pines CS/DC/RST/BUSY/SS según la tabla.
2) Arranque y logs por serie:
– En el monitor (115200), debes ver:
– [nfc-epaper-access-control] Inicio
– [WiFi] Conectando a
– [NTP] Sincronizado (si la red responde)
– [FS] LittleFS montado…
– [PN532] Chip PN5xx / Firmware x.y
– [Sistema] Listo. Acerca credencial NFC tipo A al PN532.
3) Pantalla E‑Paper inicial:
– Debe mostrar título, UID —-, la hora (si hay NTP) y el pie “Waveshare 2.9″ (SSD1680)”.
4) Presenta una tarjeta/lavero NFC:
– Acerca a ~2–4 cm del PN532.
– En menos de 1 s, la pantalla debe actualizarse parcialmente mostrando:
– UID: en hex
– Hora
– ACCESS GRANTED si su SHA‑256 está en whitelist
– ACCESS DENIED en caso contrario
– LED en GPIO2:
– Encendido fijo ~800 ms si concedido
– 3 parpadeos cortos si denegado
5) Registro de acceso:
– Reinicia y entra en modo “FS inspect” (pequeña rutina temporal o lee con LittleFS a través de un sketch; alternativamente, añade temporalmente un bloque que imprima /access_log.csv en setup).
– Debe existir /access_log.csv con entradas tipo:
– 2025-05-01 12:34:56,04a224b9c13280,248d6a…,1
6) Prueba ghosting:
– Ojo a posibles sombras tras ~10–20 operaciones parciales seguidas.
– El firmware fuerza un refresco completo cada 10 lecturas o ~60 s para limpiar ghosting.
7) Pruebas sin WiFi:
– Si no hay red, el sistema funciona igual. La hora será “1970-01-01 00:00:00” hasta que haya NTP.
8) Prueba de whitelist:
– Genera el SHA‑256 del UID con el script Python y añádelo al arreglo WHITELIST_SHA256.
– Compila y sube. Vuelve a probar: ahora esa tarjeta debe recibir ACCESS GRANTED.
Troubleshooting (errores típicos y soluciones)
1) No aparece puerto serie / no sube firmware
– Windows: instala driver CP210x (o CH340). Cambia cable USB por uno de datos.
– Linux: agrega tu usuario a grupo dialout, udev rules, o usa sudo temporalmente.
– “Failed to connect to ESP32”: mantén BOOT presionado al iniciar upload y suelta cuando empiece, o pulsa EN/RESET justo después.
– Revisa que el switch de alimentación de la placa (si lo tuviera) esté en ON.
2) “[PN532] No se detecto el PN532”
– Verifica que el PN532 está en modo SPI (algunas placas tienen switch/puentes SEL).
– Comprueba SS en GPIO15 y el cableado SCK/MOSI/MISO compartiendo VSPI.
– Alimentación: usa 3V3 estable; evita cables flojos.
– Asegúrate de no usar IRQ si el sketch no lo configura (nuestro ejemplo usa polling).
3) E‑Paper bloqueado en “BUSY” (no actualiza)
– BUSY mal cableado (GPIO4 en este diseño). Verifica continuidad y pin correcto.
– DC/RST invertidos: revisa DC (GPIO17) y RST (GPIO16).
– Usa el modelo adecuado de GxEPD2: GxEPD2_290 corresponde a SSD1680 2.9″ 296×128.
– Alimentación insuficiente durante refresco: intenta refresco con USB directo a PC o cargador 5V 1A.
4) Ghosting persistente en la pantalla
– Aumenta frecuencia de refrescos completos (por ejemplo cada 5 operaciones).
– Evita texto en posiciones que cambian píxel a píxel en parciales excesivos.
– Llama a drawAccessScreen(…, false) después de una serie de parciales intensos.
5) “Brownout detector triggered”
– Fuente USB débil o cable demasiado largo/fino. Cambia a puerto USB de mayor potencia o cargador 5V 2A.
– Evita alimentar otros módulos de alto consumo desde el 3V3 si no es necesario.
6) No se sincroniza NTP
– Revisa SSID/clave; verifica que el firewall permite NTP/UDP 123.
– Cambia servidor NTP (por ejemplo “time.google.com”, “es.pool.ntp.org”).
– Asegura buena potencia de señal WiFi.
7) No se imprime/crea access_log.csv
– Asegúrate de LittleFS.begin(true) en setup; se formatea automáticamente si no existe.
– Comprueba espacio: aunque improbable en pruebas, evita generar archivos demasiado grandes sin depuración.
8) El UID se muestra pero nunca “GRANTED”
– Verifica que el SHA‑256 del UID está correcto:
– Cuidado con mayúsculas/minúsculas; nuestro código usa hex minúscula.
– Asegúrate de no añadir espacios/nuevas líneas al copiar el hash.
– Algunas tarjetas tienen UIDs de longitud distinta (4/7 bytes). El hash depende exactamente de los bytes y el orden; usa el script provisto.
Mejoras / variantes
- Persistencia de whitelist:
- Cargar/guardar desde LittleFS en JSON/CSV para no recompilar al añadir tarjetas.
- Herramienta de administración por puerto serie o web (microservidor HTTP en el ESP32).
- Seguridad avanzada:
- HMAC‑SHA256 con “salt” y clave única por instalación; evita incluso ataques de diccionario sobre UIDs.
- Generar códigos de un solo uso (OTP) mostrados temporalmente en E‑Paper tras verificación NFC.
- UX del panel:
- Añadir iconografía o inversos de color en respuestas (negro sobre blanco y viceversa).
- Temporizador de autoapagado y despertar por botón (profundizar en consumo).
- Red y backend:
- Sincronizar logs a un servidor remoto (HTTPS con WiFiClientSecure).
- Integración con MQTT para auditoría centralizada.
- Hardware:
- Añadir un relé/SSR controlado por ESP32 al conceder acceso (con las debidas protecciones).
- Añadir buzzer piezoactivo para feedback audible (GPIO dedicado).
- Modo offline robusto:
- RTC externo (p.ej., DS3231) para mantener hora sin NTP.
- Cifrado de whitelist en flash y bloqueo por “secure boot”/flash encryption (ecosistema ESP-IDF).
Checklist de verificación
- [ ] He instalado PlatformIO Core 6.1.13 y Python 3.11.6.
- [ ] He creado el proyecto con board = nodemcu-32s y platform = espressif32 @ 6.6.0.
- [ ] He pegado platformio.ini con librerías y paquetes en las versiones indicadas.
- [ ] He cableado correctamente el E‑Paper (SSD1680) a GPIO23/18/5/17/16/4 y 3V3/GND.
- [ ] He cableado el PN532 (SPI) a GPIO23/18/19/15 y 3V3/GND, y está en modo SPI.
- [ ] He configurado WIFI_SSID/WIFI_PASSWORD (opcional) en main.cpp.
- [ ] He generado y añadido el SHA‑256 de al menos un UID a WHITELIST_SHA256.
- [ ] La compilación pio run finaliza sin errores.
- [ ] La carga pio run -t upload se completa; el monitor serie muestra el banner de inicio.
- [ ] Al acercar un tag NFC se muestra UID y “ACCESS GRANTED/DENIED” en el E‑Paper.
- [ ] Se registra el evento en /access_log.csv con timestamp, UID y resultado.
- [ ] He verificado refrescos completos periódicos (ghosting controlado).
Con este caso práctico “nfc-epaper-access-control” has integrado lectura NFC con PN532, render en e‑paper Waveshare 2.9″ (SSD1680), y control de acceso con verificación criptográfica en un ESP32 NodeMCU-32S, cuidando reproducibilidad con PlatformIO y un pipeline de build/flash/monitor determinista.
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.



