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
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/5 | Número GPIO | Función | PN532 (NFC) | DS3231 (RTC) | Pantalla E-Paper |
|---|---|---|---|---|---|
| Pin 1 | Alimentación 3.3V | VCC | VCC | VCC | VCC |
| Pin 6 | Tierra (GND) | GND | GND | GND | GND |
| Pin 3 | GPIO 2 | I2C SDA | SDA | SDA | – |
| Pin 5 | GPIO 3 | I2C SCL | SCL | SCL | – |
| Pin 19 | GPIO 10 | SPI MOSI | – | – | DIN (Entrada de datos) |
| Pin 23 | GPIO 11 | SPI SCLK | – | – | CLK (Reloj) |
| Pin 24 | GPIO 8 | SPI CE0 | – | – | CS (Selector de chip) |
| Pin 22 | GPIO 25 | Salida GPIO | – | – | DC (Datos/Comandos) |
| Pin 11 | GPIO 17 | Salida GPIO | – | – | RST (Restablecimiento) |
| Pin 18 | GPIO 24 | Entrada GPIO | – | – | BUSY (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
# ...#!/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
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.




