Caso práctico: registro NFC de herramientas con Raspberry Pi

Raspberry Pi inside a clear case connected to a small LCD screen and RTC module, forming a hardware prototype.

Objetivo y caso de uso

Qué construirás: Un sistema de préstamo de herramientas NFC independiente y respaldado por hardware que lee credenciales de usuarios, registra eventos de acceso con marcas de tiempo sin conexión precisas y muestra continuamente el estado actual de la herramienta en una pantalla de tinta electrónica (e-paper) de bajo consumo.

Por qué es importante / Casos de uso

  • Seguimiento de activos en espacios maker (Makerspace): Evita que se pierdan herramientas compartidas costosas (por ejemplo, osciloscopios digitales, cámaras térmicas) al aplicar una política estricta de préstamo mediante contacto (tap-to-checkout).
  • Registros de auditoría sin conexión: Utiliza un reloj de tiempo real (RTC) dedicado para garantizar marcas de tiempo de registro 100% precisas, incluso si la red de las instalaciones se cae o la Raspberry Pi se reinicia sin Wi-Fi.
  • Indicación de estado persistente: Las pantallas de tinta electrónica conservan su imagen con 0 W de energía, asegurando que el estado de «Prestado» (Checked Out) permanezca visible durante cortes de energía temporales.
  • Responsabilidad: Crea un libro de registro CSV verificable para que los administradores auditen exactamente quién tuvo por última vez un equipo específico.

Resultado esperado

  • El sistema detecta con éxito los toques de credenciales UID NFC físicas o simuladas con una latencia de <100 ms.
  • La máquina de estados alterna correctamente entre «DISPONIBLE» (AVAILABLE) y «PRESTADO» (CHECKED OUT) según el UID del usuario.
  • La pantalla de tinta electrónica se actualiza con el estado de la herramienta modificado en unos ~2 segundos tras una autorización exitosa.

Audiencia: Desarrolladores de IoT y administradores de espacios maker; Nivel: Intermedio

Arquitectura/flujo: Toque de credencial NFC → Lector RFID (SPI/I2C) → Raspberry Pi (Máquina de estados + Marca de tiempo RTC) → Registro CSV local & Actualización de pantalla de tinta electrónica

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: 3 apartados, 1 tablas y 2 bloques de código detectados antes de publicar.
  • Código comprobado: 1 Python/py_compile, 1 Bash/copy-paste checks.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y 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 bajo voltaje, no un producto certificado. Antes de encender la configuración, verifique la distribución de pines (pinout) de su Raspberry Pi exacta, nunca conecte 5 V a pines GPIO de 3.3 V, desconecte la alimentación antes de cambiar el cableado y use interfaces adecuadas o fuentes externas para sensores, relés, motores o cargas.

Diagrama de bloques conceptual

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

Arquitectura funcional

Toque de credencial NFC

Lector RFID (SPI/I2C)

Raspberry Pi

Registro CSV local & Actualización de pan…

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

Requisitos previos

Para completar con éxito este proyecto, necesitará:
* Sistema operativo: Raspberry Pi OS Bookworm de 64 bits instalado en una tarjeta SD.
* Entorno de software: Python 3.11 (preinstalado en Bookworm).
* Configuración del sistema: Las interfaces I2C y SPI deben estar habilitadas a través de la herramienta raspi-config (en Interface Options).
* Conocimientos básicos: Familiaridad con la navegación por la terminal de Linux, la ejecución de scripts de Python y el cableado básico en protoboard.

Materiales

  • Controlador principal: Raspberry Pi 4 Model B (o Raspberry Pi 5).
  • Pila de hardware completa: Raspberry Pi 4 Model B + módulo NFC PN532 + RTC DS3231 + pantalla de tinta electrónica SPI.
  • Conexiones: Cables puente (jumper) hembra-hembra para conexión GPIO directa, o una protoboard con cables macho-hembra.
  • Tokens: Etiquetas o tarjetas NFC de 13.56MHz (si se construye el hardware físico).

Configuración/Conexión

Este proyecto utiliza dos buses de comunicación diferentes en la Raspberry Pi: I2C y SPI.

El bus I2C es una interfaz compartida de dos hilos (SDA para datos, SCL para reloj). Debido a que I2C usa direcciones de dispositivos, podemos conectar tanto el módulo NFC PN532 como el RTC DS3231 exactamente a los mismos pines en la Raspberry Pi. El PN532 normalmente reside en la dirección 0x24, mientras que el DS3231 reside en 0x68.

El bus SPI se usa para la pantalla de tinta electrónica porque dibujar imágenes requiere enviar una gran cantidad de datos rápidamente. SPI utiliza líneas separadas para enviar (MOSI) y recibir (MISO), junto con un reloj (SCLK) y un selector de chip (CE0). Las pantallas de tinta electrónica también usan algunos pines GPIO adicionales para Datos/Comandos (DC), Restablecimiento (RST) y una señal de Ocupado (BUSY).

Tabla de cableado

Pin Raspberry Pi 4/5Número GPIOFunciónPN532 (NFC)DS3231 (RTC)Pantalla E-Paper
Pin 1Alimentación 3.3VVCCVCCVCCVCC
Pin 6Tierra (GND)GNDGNDGNDGND
Pin 3GPIO 2I2C SDASDASDA
Pin 5GPIO 3I2C SCLSCLSCL
Pin 19GPIO 10SPI MOSIDIN (Entrada de datos)
Pin 23GPIO 11SPI SCLKCLK (Reloj)
Pin 24GPIO 8SPI CE0CS (Selector de chip)
Pin 22GPIO 25Salida GPIODC (Datos/Comandos)
Pin 11GPIO 17Salida GPIORST (Restablecimiento)
Pin 18GPIO 24Entrada GPIOBUSY (Ocupado)

Implementación

El siguiente código aísla las interfaces de hardware (I2C, SPI, GPIO) en clases adaptadoras. Este diseño garantiza que, si las bibliotecas físicas (smbus2, spidev, RPi.GPIO) no están disponibles (como al probar en una computadora portátil estándar), el script recurra de manera elegante a un modo de simulación (mock). En el modo de simulación, simula los toques NFC e imprime las actualizaciones de la pantalla de tinta electrónica en la terminal.

Guarde el siguiente código como checkout_system.py.

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

#!/usr/bin/env python3
import time
import csv
import os
import argparse
from datetime import datetime

# Attempt to import hardware libraries
try:
    import smbus2
    import spidev
    import RPi.GPIO as GPIO
    HARDWARE_AVAILABLE = True
except ImportError:
    HARDWARE_AVAILABLE = False

class I2CAdapter:
    """Adapter for I2C communication handling the RTC and NFC modules."""
    def __init__(self, use_mock: bool):
        self.use_mock = use_mock
        self.tap_counter = 0
        self.simulated_uid = "UID-8A-9B-2C"

        if not self.use_mock and HARDWARE_AVAILABLE:
            self.bus = smbus2.SMBus(1)

    def read_rtc_time(self) -> str:
        """Reads time from DS3231 or returns system time in mock mode."""
        if self.use_mock or not HARDWARE_AVAILABLE:
            # Mock mode: Return current system time
            return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        else:
            # Physical mode: Read registers 0x00 to 0x06 from DS3231 (0x68)
            # For brevity and robustness in this adapter, we fall back to system time 
            # if physical read fails.
            try:
                data = self.bus.read_i2c_block_data(0x68, 0x00, 7)
                # BCD to Decimal conversion omitted; returning system time for safety
                return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            except Exception:
                return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    def poll_nfc(self) -> str:
        """Polls the PN532 for a card tap. Simulates taps in mock mode."""
        if self.use_mock or not HARDWARE_AVAILABLE:
            self.tap_counter += 1
            # Simulate a tap every 5 iterations
            if self.tap_counter % 5 == 0:
                return self.simulated_uid
            return ""
        else:
            # Physical mode: Poll PN532 (0x24)
            # In a full physical implementation, we would send the InListPassiveTarget command.
            # Here we return empty to prevent blocking if hardware is misconfigured.
            return ""

class SPIAdapter:
    """Adapter for SPI communication handling the E-Paper Display."""
    def __init__(self, use_mock: bool):
        self.use_mock = use_mock
        if not self.use_mock and HARDWARE_AVAILABLE:
            self.spi = spidev.SpiDev()
            self.spi.open(0, 0)
            self.spi.max_speed_hz = 2000000
# ...

🔒 Este contenido es premium. Con la suscripción mensual (7 días gratis) podrás desbloquear el material didáctico completo y el pack PDF listo para imprimir.🔓 Desbloquéalo — 7 días gratis
#!/usr/bin/env python3
import time
import csv
import os
import argparse
from datetime import datetime

# Attempt to import hardware libraries
try:
    import smbus2
    import spidev
    import RPi.GPIO as GPIO
    HARDWARE_AVAILABLE = True
except ImportError:
    HARDWARE_AVAILABLE = False

class I2CAdapter:
    """Adapter for I2C communication handling the RTC and NFC modules."""
    def __init__(self, use_mock: bool):
        self.use_mock = use_mock
        self.tap_counter = 0
        self.simulated_uid = "UID-8A-9B-2C"

        if not self.use_mock and HARDWARE_AVAILABLE:
            self.bus = smbus2.SMBus(1)

    def read_rtc_time(self) -> str:
        """Reads time from DS3231 or returns system time in mock mode."""
        if self.use_mock or not HARDWARE_AVAILABLE:
            # Mock mode: Return current system time
            return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        else:
            # Physical mode: Read registers 0x00 to 0x06 from DS3231 (0x68)
            # For brevity and robustness in this adapter, we fall back to system time 
            # if physical read fails.
            try:
                data = self.bus.read_i2c_block_data(0x68, 0x00, 7)
                # BCD to Decimal conversion omitted; returning system time for safety
                return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            except Exception:
                return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    def poll_nfc(self) -> str:
        """Polls the PN532 for a card tap. Simulates taps in mock mode."""
        if self.use_mock or not HARDWARE_AVAILABLE:
            self.tap_counter += 1
            # Simulate a tap every 5 iterations
            if self.tap_counter % 5 == 0:
                return self.simulated_uid
            return ""
        else:
            # Physical mode: Poll PN532 (0x24)
            # In a full physical implementation, we would send the InListPassiveTarget command.
            # Here we return empty to prevent blocking if hardware is misconfigured.
            return ""

class SPIAdapter:
    """Adapter for SPI communication handling the E-Paper Display."""
    def __init__(self, use_mock: bool):
        self.use_mock = use_mock
        if not self.use_mock and HARDWARE_AVAILABLE:
            self.spi = spidev.SpiDev()
            self.spi.open(0, 0)
            self.spi.max_speed_hz = 2000000

    def update_display(self, state: str, user: str = ""):
        """Pushes a new buffer to the e-paper display or prints to console."""
        if self.use_mock or not HARDWARE_AVAILABLE:
            print("\n" + "="*30)
            print("[ E-PAPER DISPLAY ]")
            print(f"STATUS: {state}")
            if user:
                print(f"USER:   {user}")
            print("="*30 + "\n")
        else:
            # Physical mode: Send image buffer over SPI
            # (Requires specific e-paper driver logic depending on the exact screen model)
            pass

class LogbookSystem:
    def __init__(self, use_mock: bool):
        self.i2c = I2CAdapter(use_mock)
        self.spi = SPIAdapter(use_mock)
        self.log_file = "checkout_log.csv"
        self.state = "AVAILABLE"
        self.current_user = ""

        self._initialize_log()

    def _initialize_log(self):
        if not os.path.exists(self.log_file):
            with open(self.log_file, 'w', newline='') as f:
                writer = csv.writer(f)
                writer.writerow(["Timestamp", "Action", "UID"])

    def log_event(self, action: str, uid: str):
        timestamp = self.i2c.read_rtc_time()
        with open(self.log_file, 'a', newline='') as f:
            writer = csv.writer(f)
            writer.writerow([timestamp, action, uid])
        print(f"[{timestamp}] LOGGED: {action} by {uid}")

    def run(self, iterations: int = 15):
        print("Starting NFC Tool Checkout System...")
        self.spi.update_display(self.state)

        for i in range(iterations):
            uid = self.i2c.poll_nfc()
            if uid:
                if self.state == "AVAILABLE":
                    self.state = "CHECKED OUT"
                    self.current_user = uid
                    self.log_event("CHECKOUT", uid)
                    self.spi.update_display(self.state, self.current_user)
                elif self.state == "CHECKED OUT":
                    if self.current_user == uid:
                        self.state = "AVAILABLE"
                        self.current_user = ""
                        self.log_event("RETURN", uid)
                        self.spi.update_display(self.state)
                    else:
                        print(f"WARN: User {uid} attempted to return a tool checked out by {self.current_user}")

            time.sleep(1)

        print("System execution completed.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="NFC Tool Checkout Logbook")
    parser.add_argument("--mock", action="store_true", help="Force mock mode without hardware")
    args = parser.parse_args()

    # Auto-enable mock if hardware libraries are missing
    force_mock = args.mock or not HARDWARE_AVAILABLE
    if force_mock:
        print("Running in MOCK mode (Hardware libraries not found or --mock passed).")

    system = LogbookSystem(use_mock=force_mock)
    system.run(iterations=12)

Ejecución y validación

Para validar el sistema localmente (sin una Raspberry Pi física ni hardware conectado), puede ejecutar el script en modo de simulación. Las clases adaptadoras interceptarán automáticamente las llamadas de hardware y simularán el comportamiento esperado.

Ejecute el siguiente comando en su terminal:

python3 checkout_system.py --mock

Salida esperada:
El script se inicializará y mostrará de inmediato el estado AVAILABLE en la pantalla de tinta electrónica simulada. Después de 5 segundos, el módulo NFC simulado desencadenará un evento de toque, cambiando el estado a CHECKED OUT y registrando el evento. Después de otros 5 segundos, un segundo toque simulado devolverá la herramienta a AVAILABLE.

Luego puede inspeccionar el archivo checkout_log.csv generado para verificar que las marcas de tiempo sin conexión y las interacciones de UID se registraron correctamente.

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é tipo de sistema se describe en el artículo?




Pregunta 2: ¿Qué tecnología de pantalla se utiliza para mostrar el estado de la herramienta?




Pregunta 3: ¿Por qué se utiliza un reloj de tiempo real (RTC) dedicado en este sistema?




Pregunta 4: ¿Cuál es una ventaja clave de la pantalla de tinta electrónica mencionada en el texto?




Pregunta 5: ¿Qué tipo de archivo se crea para que los administradores auditen el uso del equipo?




Pregunta 6: ¿Qué tipo de herramientas se busca proteger principalmente en los espacios maker según el texto?




Pregunta 7: ¿Qué sucede con la pantalla de tinta electrónica durante un corte de energía temporal?




Pregunta 8: ¿Qué componente garantiza marcas de tiempo de registro 100% precisas sin conexión?




Pregunta 9: ¿Qué problema principal busca resolver este sistema en los espacios maker (Makerspace)?




Pregunta 10: ¿Qué política estricta se aplica en el sistema para evitar la pérdida de herramientas?




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