You dont have javascript enabled! Please enable it!

Caso práctico: control NFC con Raspberry Pi 4

Caso práctico: control NFC con Raspberry Pi 4 — hero

Objetivo y caso de uso

Qué construirás: Un prototipo con Raspberry Pi 4 Model B que lee tarjetas NFC mediante un HAT PN532, comprueba cada UID contra una lista de permitidos, marca con fecha y hora cada intento usando un RTC DS3231 y cambia a un estado de alarma cuando se detecta una etiqueta no autorizada. El sistema está diseñado para tomar decisiones locales rápidas, con un manejo típico de lectura de tarjeta en menos de 200 ms y baja carga en reposo sobre la Pi.

Por qué importa / Casos de uso

  • Control de acceso a un armario de taller o laboratorio pequeño: Monta el lector en un armario de herramientas, cajón de electrónica o taquilla de proyectos para que solo las tarjetas aprobadas de estudiantes o personal permitan el acceso.
  • Alarma educativa de entrada para un rincón de makerspace: Las tarjetas desconocidas pueden activar una marca de alarma por software casi en tiempo real, adecuada para conectarla más adelante a un zumbador, relé, baliza LED o notificador por webhook.
  • Registro fiable de eventos incluso sin internet: El DS3231 mantiene la hora exacta sin conexión, de modo que los intentos denegados y permitidos siguen teniendo marcas de tiempo útiles durante caídas de Wi‑Fi o en funcionamiento de laboratorio aislado.
  • Rastro de auditoría simple para equipos compartidos: Guarda los eventos en CSV o JSON con UID de tarjeta, marca de tiempo y resultado para revisar quién intentó acceder y cuándo.
  • Prototipo edge de baja sobrecarga: Esto funciona cómodamente en una Raspberry Pi 4 con demanda mínima de CPU y un uso de GPU efectivamente del 0 %, lo que lo hace práctico para monitorización permanente.

Resultado esperado

  • Un verificador de acceso NFC funcional que clasifica las etiquetas presentadas como autorizadas o no autorizadas.
  • Registros precisos respaldados por RTC para cada escaneo, incluidas sesiones sin conexión y reinicios.
  • Un estado de alarma por software que se activa ante accesos denegados y que puede ampliarse a salidas físicas.
  • Un tiempo de respuesta extremo a extremo de referencia de unos 100-200 ms por escaneo, según el intervalo de sondeo y las escrituras de almacenamiento.
  • Un proyecto inicial reutilizable para cerraduras de armarios, activos de laboratorio, puntos de control de asistencia o demostraciones de alerta de entrada.

Público: Estudiantes, makers y desarrolladores principiantes de embebidos/Linux que construyen demostraciones de control de acceso; Nivel: Principiante a intermedio

Arquitectura/flujo: PN532 lee el UID NFC → la aplicación de Raspberry Pi comprueba la lista local de permitidos → DS3231 proporciona la marca de tiempo → el evento se escribe en un registro CSV/JSON → un escaneo autorizado marca acceso concedido, un escaneo no autorizado activa el estado de alarma.

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. El validador comprobó los bloques de código, la estructura del artículo, los comandos copiables y la coherencia con el catálogo de dispositivos soportados.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 48 apartados, 1 tablas y 29 bloques de código detectados en el contenido publicado.
  • Código comprobado: 2 Python/py_compile, 23 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo; los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el material publicado, pero no sustituye la prueba física sobre tu hardware, cableado y entorno exactos.

Nota educativa de seguridad

Este proyecto es un prototipo educativo de control de acceso, no un sistema de seguridad certificado. Ten en cuenta estos límites:

  • No confíes en él como única protección para bienes valiosos, infraestructura crítica o seguridad personal.
  • El tutorial se centra en aprender interfaces, registro y lógica de acceso.
  • No conectes tensión de red directamente a la Raspberry Pi ni al cableado de protoboard.
  • Si más adelante añades una sirena, cerradura o relé, utiliza solo módulos de baja tensión con aislamiento adecuado y sigue la documentación del módulo.
  • No asumas que la salida de alarma de este tutorial puede accionar directamente una cerradura o sirena real.
  • El tutorial actual usa un estado de alarma por software y una indicación en consola.
  • Protege la Raspberry Pi frente a errores de cableado.
  • Usa periféricos compatibles con 3.3 V y comprueba dos veces las etiquetas de los pines antes de encender.
  • El mecanismo NFC mostrado aquí no está reforzado contra clonación, repetición, manipulación o bypass físico.
  • Trátalo como una plataforma de enseñanza, no como un sistema comercial de acceso seguro.
  • Si lo instalas cerca de una puerta real, asegúrate de que siempre haya una anulación manual segura y un uso legal.
  • Nunca crees una configuración que pueda atrapar a personas o bloquear rutas de salida de emergencia.

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

PN532 lee el UID NFC

la aplicación de Raspberry Pi comprueba l…

DS3231 proporciona la marca de tiempo

el evento se escribe en un registro CSV/JSON

un escaneo autorizado marca acceso conced…

Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.

Requisitos previos

Antes de comenzar, prepara la Raspberry Pi y el entorno básico de software.

  1. Requisitos previos de hardware
  2. Raspberry Pi 4 Model B
  3. Tarjeta MicroSD con Raspberry Pi OS Bookworm 64-bit
  4. HAT NFC PN532
  5. Módulo RTC DS3231 o RTC integrado en el HAT expuesto por I2C
  6. Fuente de alimentación estable de 5 V para Raspberry Pi
  7. Tarjeta o etiqueta NFC para pruebas

  8. Requisitos previos de software

  9. Raspberry Pi OS Bookworm 64-bit
  10. Python 3.11
  11. Acceso a terminal en la Pi
  12. I2C y SPI habilitados en la configuración de Raspberry Pi

  13. Habilidades asumidas

  14. Editar archivos con nano u otro editor de texto
  15. Ejecutar comandos en una shell
  16. Leer con cuidado las etiquetas de los pines GPIO

Comprueba la versión de Python:

python3 --version

El resultado esperado en Bookworm debería ser similar a:

Python 3.11.x

Materiales

Usa el modelo exacto de hardware solicitado.

ElementoModelo exacto / requisitoPropósito
Placa principalRaspberry Pi 4 Model BEjecuta el software de control de acceso
Lector NFCHAT NFC PN532Lee tarjetas/etiquetas NFC
Reloj en tiempo realRTC DS3231Proporciona marcas de tiempo estables
AlimentaciónFuente oficial o de buena calidad de 5 V para Raspberry Pi 4Funcionamiento estable
AlmacenamientoTarjeta MicroSD con Raspberry Pi OS Bookworm 64-bitSistema operativo y registros
Medio de pruebaAl menos 1 etiqueta NFC autorizada y 1 etiqueta NFC no autorizadaValidación
Salida opcionalPequeño zumbador activo o LED mediante interfaz segura de baja tensiónIndicador físico de alarma más adelante

Configuración/Conexión

Este proyecto evita un diagrama de circuito y usa solo indicaciones de conexión en texto.

Estrategia de conexión

El prototipo usa:
SPI para el HAT NFC PN532
I2C para el RTC DS3231

Muchas placas HAT PN532 pueden configurarse para SPI, I2C o UART usando interruptores/jumpers. Para este tutorial:
– Configura el HAT PN532 en modo SPI
– Mantén el DS3231 en I2C

Pasos para habilitar interfaces en Raspberry Pi

Ejecuta:

sudo raspi-config

Después:
1. Ve a Interface Options
2. Habilita SPI
3. Habilita I2C
4. Finaliza y reinicia

Después del reinicio, verifica:

ls /dev/spidev*
ls /dev/i2c-*

Deberías ver dispositivos similares a:
/dev/spidev0.0
/dev/i2c-1

Notas de conexión basadas en texto

HAT NFC PN532

Si tu HAT PN532 se apila directamente sobre el encabezado de la Raspberry Pi, los pines SPI ya están encaminados a través del encabezado. Si usas cables en lugar de un apilado HAT directo, conecta estas señales:

  • PN532 VCC -> Raspberry Pi 3.3 V
  • PN532 GND -> Raspberry Pi GND
  • PN532 SCK -> Raspberry Pi SPI SCLK
  • PN532 MISO -> Raspberry Pi SPI MISO
  • PN532 MOSI -> Raspberry Pi SPI MOSI
  • PN532 SS/CS -> Raspberry Pi SPI CE0
  • PN532 RSTO or RSTPDN -> GPIO opcional si tu placa lo requiere; de lo contrario, déjalo según el diseño del HAT
  • Selector de modo PN532 -> SPI

RTC DS3231

Si el RTC es un módulo independiente:
– DS3231 VCC -> Raspberry Pi 3.3 V
– DS3231 GND -> Raspberry Pi GND
– DS3231 SDA -> Raspberry Pi GPIO2 / SDA1
– DS3231 SCL -> Raspberry Pi GPIO3 / SCL1

Comprobaciones de detección de buses

Instala herramientas comunes:

sudo apt update
sudo apt install -y i2c-tools python3-pip

Comprueba los dispositivos I2C:

sudo i2cdetect -y 1

Un DS3231 suele aparecer alrededor de la dirección 0x68.

Para SPI, no hay una sonda única equivalente tan sencilla como i2cdetect, pero la existencia de /dev/spidev0.0 confirma que la interfaz SPI está habilitada.

Directorio del proyecto

Crea un directorio de trabajo limpio:

mkdir -p ~/nfc-door-access-alarm
cd ~/nfc-door-access-alarm

Código validado

El código siguiente está diseñado para cumplir dos objetivos importantes:
1. Ser útil en la Raspberry Pi real con clases adaptadoras de hardware.
2. Poder ejecutarse en modo dry-run/mock en un ordenador normal sin hardware NFC ni RTC.

Esto coincide con el estilo de validación solicitado para Raspberry Pi.

access_controller.py

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

#!/usr/bin/env python3
from __future__ import annotations

import argparse
import csv
import json
import os
import sys
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Iterable, List, Optional


@dataclass
class AccessEvent:
    timestamp: str
    uid: str
    authorized: bool
    source: str
    alarm_active: bool


class RTCAdapter:
    def now_iso(self) -> str:
        raise NotImplementedError


class SystemRTC(RTCAdapter):
    def now_iso(self) -> str:
        return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")


class MockDS3231RTC(RTCAdapter):
    def __init__(self, fixed_time: Optional[str] = None) -> None:
        self.fixed_time = fixed_time

    def now_iso(self) -> str:
        if self.fixed_time:
            return self.fixed_time
        return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")


class NFCReaderAdapter:
    def poll_uid(self) -> Optional[str]:
        raise NotImplementedError


class MockPN532Reader(NFCReaderAdapter):
    def __init__(self, sequence: Iterable[str], repeat: bool = False) -> None:
        self._sequence = list(sequence)
        self._repeat = repeat
        self._index = 0

    def poll_uid(self) -> Optional[str]:
        if not self._sequence:
            return None
        if self._index >= len(self._sequence):
            if self._repeat:
                self._index = 0
            else:
                return None
        uid = self._sequence[self._index]
        self._index += 1
        time.sleep(0.2)
        return uid


class AlarmAdapter:
    def set_alarm(self, active: bool) -> None:
        raise NotImplementedError


class ConsoleAlarm(AlarmAdapter):
    def __init__(self) -> None:
        self.state = False

    def set_alarm(self, active: bool) -> None:
        if active != self.state:
            self.state = active
            print(f"[ALARM] state={'ON' if active else 'OFF'}")


class AccessController:
    def __init__(
        self,
        rtc: RTCAdapter,
        reader: NFCReaderAdapter,
        alarm: AlarmAdapter,
        allowed_uids: List[str],
        log_path: Path,
    ) -> None:
        self.rtc = rtc
        self.reader = reader
        self.alarm = alarm
        self.allowed_uids = {uid.strip().upper() for uid in allowed_uids if uid.strip()}
        self.log_path = log_path
        self.alarm_active = False
# ... continúa para miembros en el código completo validado ...

🔒 Parte del código validado es premium. Con el pase de 7 días o la suscripción mensual podrás consultar el archivo completo validado.

#!/usr/bin/env python3
from __future__ import annotations

import argparse
import csv
import json
import os
import sys
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Iterable, List, Optional


@dataclass
class AccessEvent:
    timestamp: str
    uid: str
    authorized: bool
    source: str
    alarm_active: bool


class RTCAdapter:
    def now_iso(self) -> str:
        raise NotImplementedError


class SystemRTC(RTCAdapter):
    def now_iso(self) -> str:
        return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")


class MockDS3231RTC(RTCAdapter):
    def __init__(self, fixed_time: Optional[str] = None) -> None:
        self.fixed_time = fixed_time

    def now_iso(self) -> str:
        if self.fixed_time:
            return self.fixed_time
        return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")


class NFCReaderAdapter:
    def poll_uid(self) -> Optional[str]:
        raise NotImplementedError


class MockPN532Reader(NFCReaderAdapter):
    def __init__(self, sequence: Iterable[str], repeat: bool = False) -> None:
        self._sequence = list(sequence)
        self._repeat = repeat
        self._index = 0

    def poll_uid(self) -> Optional[str]:
        if not self._sequence:
            return None
        if self._index >= len(self._sequence):
            if self._repeat:
                self._index = 0
            else:
                return None
        uid = self._sequence[self._index]
        self._index += 1
        time.sleep(0.2)
        return uid


class AlarmAdapter:
    def set_alarm(self, active: bool) -> None:
        raise NotImplementedError


class ConsoleAlarm(AlarmAdapter):
    def __init__(self) -> None:
        self.state = False

    def set_alarm(self, active: bool) -> None:
        if active != self.state:
            self.state = active
            print(f"[ALARM] state={'ON' if active else 'OFF'}")


class AccessController:
    def __init__(
        self,
        rtc: RTCAdapter,
        reader: NFCReaderAdapter,
        alarm: AlarmAdapter,
        allowed_uids: List[str],
        log_path: Path,
    ) -> None:
        self.rtc = rtc
        self.reader = reader
        self.alarm = alarm
        self.allowed_uids = {uid.strip().upper() for uid in allowed_uids if uid.strip()}
        self.log_path = log_path
        self.alarm_active = False

    def handle_uid(self, uid: str, source: str = "nfc") -> AccessEvent:
        normalized = uid.strip().upper()
        authorized = normalized in self.allowed_uids
        self.alarm_active = not authorized
        self.alarm.set_alarm(self.alarm_active)

        event = AccessEvent(
            timestamp=self.rtc.now_iso(),
            uid=normalized,
            authorized=authorized,
            source=source,
            alarm_active=self.alarm_active,
        )
        self._append_log(event)
        if authorized:
            print(f"{event.timestamp} ACCESS GRANTED uid={event.uid}")
        else:
            print(f"{event.timestamp} ACCESS DENIED uid={event.uid}")
        return event

    def _append_log(self, event: AccessEvent) -> None:
        file_exists = self.log_path.exists()
        with self.log_path.open("a", newline="", encoding="utf-8") as f:
            writer = csv.writer(f)
            if not file_exists:
                writer.writerow(["timestamp", "uid", "authorized", "source", "alarm_active"])
            writer.writerow([
                event.timestamp,
                event.uid,
                int(event.authorized),
                event.source,
                int(event.alarm_active),
            ])

    def run(self, max_reads: int = 0, poll_delay: float = 0.5) -> int:
        reads = 0
        while True:
            uid = self.reader.poll_uid()
            if uid:
                self.handle_uid(uid)
                reads += 1
            else:
                time.sleep(poll_delay)

            if max_reads > 0 and reads >= max_reads:
                break
        return 0


def load_allowed_uids(path: Path) -> List[str]:
    with path.open("r", encoding="utf-8") as f:
        data = json.load(f)
    if not isinstance(data, dict) or "allowed_uids" not in data:
        raise ValueError("allowlist file must contain a JSON object with key 'allowed_uids'")
    items = data["allowed_uids"]
    if not isinstance(items, list):
        raise ValueError("'allowed_uids' must be a list")
    return [str(x) for x in items]


def build_mock_sequence(args: argparse.Namespace) -> List[str]:
    if args.mock_sequence:
        return [x.strip().upper() for x in args.mock_sequence.split(",") if x.strip()]
    return [
        "04A1B2C3D4",
        "1122334455",
        "04A1B2C3D4",
    ]


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="NFC door access alarm prototype")
    parser.add_argument("--allowlist", default="allowlist.json", help="Path to JSON allowlist")
    parser.add_argument("--log", default="access_log.csv", help="Path to CSV log file")
    parser.add_argument("--mock", action="store_true", help="Use mock NFC and RTC adapters")
    parser.add_argument("--mock-sequence", default="", help="Comma-separated UID sequence for mock mode")
    parser.add_argument("--max-reads", type=int, default=3, help="Stop after this many successful reads, 0=forever")
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    allowlist_path = Path(args.allowlist)
    log_path = Path(args.log)

    allowed_uids = load_allowed_uids(allowlist_path)

    if args.mock:
        rtc: RTCAdapter = MockDS3231RTC()
        reader: NFCReaderAdapter = MockPN532Reader(build_mock_sequence(args), repeat=False)
    else:
        # real-hardware adapter integration is intentionally not auto-imported here.
        # If no mock mode is selected, use system clock and require explicit future extension
        # for PN532 hardware reading.
        rtc = SystemRTC()
        reader = MockPN532Reader([], repeat=False)

    alarm = ConsoleAlarm()
    controller = AccessController(
        rtc=rtc,
        reader=reader,
        alarm=alarm,
        allowed_uids=allowed_uids,
        log_path=log_path,
    )
    return controller.run(max_reads=args.max_reads)


if __name__ == "__main__":
    sys.exit(main())

allowlist.json

{
  "allowed_uids": [
    "04A1B2C3D4",
    "AABBCCDDEE"
  ]
}

test_access_controller.py

Este script de validación realiza una comprobación dry-run usando entradas simuladas. No es una dependencia de framework completo de pruebas unitarias; usa solo la biblioteca estándar.

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

#!/usr/bin/env python3
from __future__ import annotations

import csv
import subprocess
import sys
from pathlib import Path


def main() -> int:
    project_dir = Path(__file__).resolve().parent
    log_path = project_dir / "test_access_log.csv"

    if log_path.exists():
        log_path.unlink()

    cmd = [
        sys.executable,
        str(project_dir / "access_controller.py"),
        "--mock",
        "--allowlist",
        str(project_dir / "allowlist.json"),
        "--log",
        str(log_path),
        "--mock-sequence",
        "04A1B2C3D4,DEADBEEF01",
        "--max-reads",
        "2",
    ]

    result = subprocess.run(cmd, capture_output=True, text=True, check=False)
    print(result.stdout)
# ... continúa para miembros en el código completo validado ...

🔒 Parte del código validado es premium. Con el pase de 7 días o la suscripción mensual podrás consultar el archivo completo validado.

#!/usr/bin/env python3
from __future__ import annotations

import csv
import subprocess
import sys
from pathlib import Path


def main() -> int:
    project_dir = Path(__file__).resolve().parent
    log_path = project_dir / "test_access_log.csv"

    if log_path.exists():
        log_path.unlink()

    cmd = [
        sys.executable,
        str(project_dir / "access_controller.py"),
        "--mock",
        "--allowlist",
        str(project_dir / "allowlist.json"),
        "--log",
        str(log_path),
        "--mock-sequence",
        "04A1B2C3D4,DEADBEEF01",
        "--max-reads",
        "2",
    ]

    result = subprocess.run(cmd, capture_output=True, text=True, check=False)
    print(result.stdout)
    if result.returncode != 0:
        print(result.stderr)
        return result.returncode

    if not log_path.exists():
        print("ERROR: log file was not created")
        return 1

    with log_path.open("r", encoding="utf-8", newline="") as f:
        rows = list(csv.DictReader(f))

    if len(rows) != 2:
        print(f"ERROR: expected 2 log entries, got {len(rows)}")
        return 1

    if rows[0]["authorized"] != "1":
        print("ERROR: first UID should be authorized")
        return 1

    if rows[1]["authorized"] != "0":
        print("ERROR: second UID should be unauthorized")
        return 1

    if rows[1]["alarm_active"] != "1":
        print("ERROR: alarm should be active for unauthorized UID")
        return 1

    print("Dry-run validation passed.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Comandos de compilación/grabación/ejecución

Este es un proyecto Python para Raspberry Pi, por lo que no hay un paso de grabación de firmware. En su lugar, crea los archivos, valida la sintaxis y ejecuta.

1) Instalar soporte de paquetes y comprobar importaciones

cd ~/nfc-door-access-alarm
python3 --version
python3 -c "import sys, csv, json, argparse, pathlib; print('standard-library-import-check: OK')"

2) Guardar los archivos

Crea la aplicación principal:

nano access_controller.py

Crea la lista de permitidos:

nano allowlist.json

Crea el script de validación:

nano test_access_controller.py

3) Validar sintaxis de Python

python3 -m py_compile access_controller.py test_access_controller.py

Si tiene éxito, este comando no imprime nada.

4) Ejecutar la validación dry-run en Raspberry Pi o en cualquier ordenador normal

python3 test_access_controller.py

La salida esperada incluirá líneas similares a:

2026-... ACCESS GRANTED uid=04A1B2C3D4
[ALARM] state=ON
2026-... ACCESS DENIED uid=DEADBEEF01
Dry-run validation passed.

5) Ejecutar manualmente el prototipo principal en modo mock

python3 access_controller.py --mock --allowlist allowlist.json --log access_log.csv --mock-sequence 04A1B2C3D4,1122334455,04A1B2C3D4 --max-reads 3

6) Inspeccionar los registros de acceso generados

cat access_log.csv

Estructura esperada:

timestamp,uid,authorized,source,alarm_active
2026-...,04A1B2C3D4,1,nfc,0
2026-...,1122334455,0,nfc,1
2026-...,04A1B2C3D4,1,nfc,0

Validación paso a paso

Esta sección valida el proyecto en torno al objetivo real: acceso controlado por NFC con alarma y registro con marca de tiempo.

1) Confirmar sistema operativo y versión de Python

Ejecuta:

uname -a
python3 --version

Debes tener:
– Raspberry Pi OS Bookworm 64-bit
– Python 3.11.x

2) Confirmar que los buses necesarios están habilitados

Ejecuta:

ls /dev/spidev*
ls /dev/i2c-*

Criterios de éxito:
– Existe el dispositivo SPI para planificar la ruta del PN532
– Existe el dispositivo I2C para planificar la ruta del DS3231

3) Confirmar visibilidad del RTC en I2C

Ejecuta:

sudo i2cdetect -y 1

Criterios de éxito:
– Aparece un dispositivo en 68 u otra dirección RTC esperada según tu hardware

Lo que esto demuestra:
– El DS3231 es eléctricamente accesible en el bus I2C

Lo que aún no demuestra:
– Que la aplicación esté leyendo activamente la hora RTC desde un controlador de hardware dedicado en esta versión del tutorial

4) Confirmar validez sintáctica del código

Ejecuta:

python3 -m py_compile access_controller.py test_access_controller.py

Criterios de éxito:
– No se informan errores

Esto demuestra:
– Los archivos Python son sintácticamente válidos

No demuestra:
– Éxito real de transacción con el PN532

5) Validar la lógica de decisión de acceso en modo mock

Ejecuta:

python3 access_controller.py --mock --allowlist allowlist.json --log access_log.csv --mock-sequence 04A1B2C3D4,CAFEBABE00 --max-reads 2

Criterios de éxito:
– El primer evento imprime ACCESS GRANTED
– El segundo evento imprime ACCESS DENIED
– La consola muestra que la alarma cambia a ON para acceso no autorizado
– Se crea access_log.csv

6) Validar la estructura del registro

Ejecuta:

cat access_log.csv

Comprueba que haya:
– Fila de cabecera
– Exactamente dos filas de eventos
– Evento autorizado marcado como 1
– Evento no autorizado marcado como 0
– El evento no autorizado tiene alarm_active igual a 1

7) Ejecutar el validador automático dry-run incluido

Ejecuta:

python3 test_access_controller.py

Criterios de éxito:
– Línea final: Dry-run validation passed.

8) Siguiente paso con hardware real

En un aula, la siguiente ampliación práctica es sustituir MockPN532Reader por un adaptador SPI real para PN532 y sustituir MockDS3231RTC por un lector DS3231. La lógica central, el registro de eventos y el comportamiento de alarma permanecen iguales, así que validas el hardware por capas en lugar de depurar todo a la vez.

Solución de problemas

La Pi no muestra /dev/spidev0.0

  • Vuelve a ejecutar sudo raspi-config
  • Habilita SPI otra vez
  • Reinicia
  • Comprueba si otro overlay o configuración deshabilitó SPI

i2cdetect no muestra la dirección 68

  • Vuelve a comprobar el cableado del DS3231:
  • SDA a GPIO2
  • SCL a GPIO3
  • GND común
  • Alimentación de 3.3 V
  • Algunos módulos están etiquetados para 5 V pero aun así pueden exponer líneas I2C incorrectamente para uso directo con la Pi; confirma la compatibilidad lógica de tu módulo
  • Verifica que I2C esté habilitado

py_compile informa un error de sintaxis

  • Abre de nuevo el archivo y busca:
  • Comillas faltantes
  • Indentación rota
  • Saltos de línea accidentales por copiar/pegar
  • Guarda otra vez y vuelve a ejecutar:
python3 -m py_compile access_controller.py test_access_controller.py

La prueba dry-run no crea un archivo de registro

  • Confirma que estás en la carpeta correcta
  • Comprueba permisos de archivo:
pwd
ls -l
  • Asegúrate de que allowlist.json exista y contenga JSON válido

Todas las etiquetas son denegadas

  • Asegúrate de que el UID en allowlist.json coincida exactamente con el formato esperado de la etiqueta
  • El código normaliza a mayúsculas sin espacios, así que guarda los UID en mayúsculas por claridad

El estado de alarma nunca cambia

  • En este tutorial, la alarma es un estado de software impreso en la consola
  • Si más adelante añades un zumbador o salida GPIO, confirma que tu código de salida de hardware realmente llama a set_alarm(True) y set_alarm(False)

Mejoras

Una vez que el prototipo básico funcione, puedes evolucionarlo hacia una unidad de acceso más realista.

Mejoras de software

  • Añadir integración con controlador SPI real para PN532
  • Encapsula el código específico de hardware dentro de un adaptador PN532SPIReader
  • Mantén la misma interfaz poll_uid()
  • Añadir acceso real a registros del DS3231
  • Implementa un adaptador DS3231RTC usando lecturas I2C
  • Usa conversión BCD y devuelve marcas de tiempo ISO
  • Guardar nombres de usuario
  • Amplía allowlist.json para mapear UID a nombre del propietario
  • Tiempo de espera de alarma
  • En lugar de limpiar la alarma inmediatamente en la siguiente lectura válida, mantenla activa durante un número configurable de segundos
  • Registro de manipulación
  • Cuenta intentos repetidos de tarjetas fallidas y eleva una alerta más fuerte tras tres denegaciones
  • Salida de liberación de puerta
  • Añade un módulo de relé accionado por transistor para un simulador de cerradura de baja tensión
  • Página web simple de estado
  • Sirve el estado de acceso más reciente y los registros recientes desde una interfaz web solo local

Mejoras del prototipo físico

  • Coloca la Pi, el RTC y el lector en una pequeña carcasa
  • Monta el lector NFC cerca del marco de una puerta o armario
  • Añade un LED de estado claramente etiquetado:
  • Verde para acceso concedido
  • Rojo para acceso denegado
  • Añade un zumbador de baja potencia para indicar acceso denegado
  • Usa un HAT UPS o una fuente de alimentación limpia para mejorar la fiabilidad del registro

Lista de verificación final

Usa esta lista antes de declarar el proyecto como completado:

  • [ ] Raspberry Pi OS Bookworm 64-bit está instalado en la Raspberry Pi 4 Model B
  • [ ] La versión de Python es 3.11.x
  • [ ] SPI está habilitado
  • [ ] I2C está habilitado
  • [ ] El HAT NFC PN532 está configurado en modo SPI
  • [ ] El RTC DS3231 está conectado por I2C
  • [ ] Existe la carpeta del proyecto ~/nfc-door-access-alarm
  • [ ] access_controller.py está guardado
  • [ ] allowlist.json está guardado
  • [ ] test_access_controller.py está guardado
  • [ ] python3 -m py_compile access_controller.py test_access_controller.py se ejecuta sin errores
  • [ ] python3 test_access_controller.py imprime Dry-run validation passed.
  • [ ] La ejecución manual en modo mock muestra un evento concedido y uno denegado
  • [ ] access_log.csv contiene registros con marca de tiempo
  • [ ] Entiendes que este es un prototipo educativo, no un producto de seguridad certificado

Con este prototipo, tienes una base práctica para un registrador de acceso NFC real y un controlador de alarma de puerta usando la Raspberry Pi 4 Model B + HAT NFC PN532 + RTC DS3231.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a 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.

Quiz rápido

Pregunta 1: ¿Qué placa principal se utiliza para construir el prototipo mencionado en el artículo?




Pregunta 2: ¿Qué componente se utiliza para leer las tarjetas NFC en el prototipo?




Pregunta 3: ¿Cuál es la función principal del módulo RTC DS3231 en este sistema?




Pregunta 4: ¿Qué sucede cuando el sistema detecta una etiqueta NFC no autorizada?




Pregunta 5: ¿Cuál es el tiempo típico de manejo de lectura de tarjeta en este sistema?




Pregunta 6: ¿Cuál de los siguientes es un caso de uso mencionado para este sistema?




Pregunta 7: ¿Por qué el registro de eventos es fiable incluso sin conexión a internet?




Pregunta 8: ¿Qué tipo de decisiones está diseñado para tomar el sistema?




Pregunta 9: ¿Qué dato de la tarjeta NFC se comprueba contra la lista de permitidos?




Pregunta 10: ¿A qué tipo de dispositivos se sugiere conectar la alarma educativa en el futuro?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio