Un registrador Raspberry Pi RFID MQTT anota cada lectura de credencial en una puerta, una máquina de taller o un armario de herramientas y la envía a cualquier sistema que necesite saberlo: una base de datos de registros, un dashboard o un script de alertas. En este tutorial cablearás un lector RC522, leerás etiquetas en Python, decidirás en local si una etiqueta está autorizada y publicarás eventos de acceso estructurados por MQTT, incluidas alertas para tarjetas desconocidas.
Qué vas a construir
La Pi ejecuta un único servicio en Python que:
- Consulta el lector RC522 en busca de tarjetas y llaveros MIFARE de 13,56 MHz.
- Calcula un hash del UID de cada etiqueta para que el ID en bruto nunca salga del dispositivo.
- Busca el hash en un archivo local de lista blanca y decide si se concede o se deniega el acceso.
- Publica un evento de acceso por cada lectura y una alerta aparte para las etiquetas desconocidas.
- Mantiene un mensaje retained con el "último acceso" y un estado online/offline.
La decisión de acceso se toma en la Pi, no en la nube. Así la puerta sigue funcionando cuando la red está caída; MQTT se usa para registro y notificación, que es justo en lo que destaca.
Cableado del RC522
El módulo RC522 se comunica por SPI y funciona solo a 3,3 V. Conéctalo así:
| Pin del RC522 | Pin físico de la Raspberry Pi | Función |
|---|---|---|
| SDA (SS) | 24 | SPI CE0 |
| SCK | 23 | Reloj SPI |
| MOSI | 19 | SPI MOSI |
| MISO | 21 | SPI MISO |
| IRQ | Sin conectar | |
| GND | 6 | Tierra |
| RST | 22 | GPIO25 |
| 3.3V | 1 | Alimentación |
Nunca conectes el módulo a 5 V. Activa SPI con sudo raspi-config nonint do_spi 0 y reinicia; después, ls /dev/spidev* debería mostrar /dev/spidev0.0.
Instalación de las librerías
sudo apt update
sudo apt install -y python3-venv python3-dev
mkdir -p ~/rfid-mqtt && cd ~/rfid-mqtt
python3 -m venv .venv
source .venv/bin/activate
pip install "paho-mqtt>=2.0" mfrc522 spidev
El paquete mfrc522 depende de RPi.GPIO, que no funciona en la Raspberry Pi 5. En una Pi 5, sustitúyelo por el paquete compatible rpi-lgpio dentro del entorno virtual:
pip uninstall -y RPi.GPIO
pip install rpi-lgpio
Prueba el lector antes de añadir MQTT:
python -c "from mfrc522 import SimpleMFRC522; r = SimpleMFRC522(); print('Hold a tag near the reader'); print(r.read_id())"
Acerca una tarjeta o un llavero a la antena y debería imprimirse un número largo.
Diseño de los eventos
| Topic | Payload | QoS | Retained |
|---|---|---|---|
connectmq/demo/2fa7c1/door1/access |
JSON del evento de acceso | 1 | No |
connectmq/demo/2fa7c1/door1/alert |
JSON de etiqueta desconocida | 1 | No |
connectmq/demo/2fa7c1/door1/last |
Evento más reciente | 1 | Sí |
connectmq/demo/2fa7c1/door1/status |
online / offline |
1 | Sí |
Un evento de acceso tiene este aspecto:
{
"reader": "door1",
"tag": "a3f09b7c41de2280",
"name": "Alice",
"granted": true,
"ts": 1792163300
}
El campo tag es un hash con clave del UID, no el UID en sí. En un broker público, y en los archivos de registro en general, un UID en bruto es algo que no deberías difundir, porque a partir de él se pueden clonar las tarjetas de bajo coste.
El archivo de lista blanca
Crea allowlist.json en la carpeta del proyecto. Rellenarás los hashes después de las primeras lecturas:
{
"a3f09b7c41de2280": "Alice",
"51c2e8d4097fab13": "Workshop spare fob"
}
Para dar de alta una tarjeta nueva, escanéala una vez, copia el valor tag del mensaje de alerta, añádelo al archivo con un nombre y envía al servicio un SIGHUP para que lo recargue, o simplemente reinícialo.
El script Raspberry Pi RFID MQTT
Guárdalo como rfid_mqtt.py:
#!/usr/bin/env python3
"""RC522 access logging over MQTT."""
import hashlib
import hmac
import json
import os
import signal
import time
import uuid
from pathlib import Path
import paho.mqtt.client as mqtt
import RPi.GPIO as GPIO
from mfrc522 import SimpleMFRC522
BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"rfid-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/2fa7c1/door1")
READER_NAME = os.getenv("READER_NAME", "door1")
SECRET = os.getenv("TAG_SECRET", "change-me-to-a-long-random-string").encode()
ALLOWLIST_FILE = Path(os.getenv("ALLOWLIST", "allowlist.json"))
DEBOUNCE_S = 3
reader = SimpleMFRC522()
allowlist = {}
def load_allowlist(*_):
global allowlist
try:
allowlist = json.loads(ALLOWLIST_FILE.read_text())
print(f"Loaded {len(allowlist)} allowed tags")
except (OSError, json.JSONDecodeError) as err:
print(f"Could not load allowlist: {err}")
def tag_hash(uid: int) -> str:
digest = hmac.new(SECRET, str(uid).encode(), hashlib.sha256).hexdigest()
return digest[:16]
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Connection refused: {reason_code}")
return
print(f"Connected as {CLIENT_ID}")
client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.on_connect = on_connect
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
client.max_queued_messages_set(500)
if USERNAME:
client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
client.tls_set()
load_allowlist()
signal.signal(signal.SIGHUP, load_allowlist)
client.connect(BROKER, PORT, keepalive=30)
client.loop_start()
last_tag, last_time = None, 0.0
try:
while True:
uid = reader.read_id_no_block()
if uid is None:
time.sleep(0.1)
continue
tag = tag_hash(uid)
now = time.time()
if tag == last_tag and now - last_time < DEBOUNCE_S:
continue # la tarjeta sigue apoyada en el lector
last_tag, last_time = tag, now
name = allowlist.get(tag)
event = {
"reader": READER_NAME,
"tag": tag,
"name": name,
"granted": name is not None,
"ts": int(now),
}
payload = json.dumps(event)
client.publish(f"{PREFIX}/access", payload, qos=1)
client.publish(f"{PREFIX}/last", payload, qos=1, retain=True)
if name is None:
client.publish(f"{PREFIX}/alert", payload, qos=1)
print(event)
# Acciona aquí un cerradero eléctrico o un LED con GPIO.output() si se concede el acceso.
except KeyboardInterrupt:
pass
finally:
client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(2)
client.loop_stop()
client.disconnect()
GPIO.cleanup()
El antirrebote evita que una tarjeta apoyada en el lector genere un evento cada décima de segundo. Como paho guarda en memoria los mensajes QoS 1 mientras la conexión está caída, las lecturas realizadas durante un corte de red breve se entregan tras la reconexión; max_queued_messages_set limita esa cola para que un corte largo no agote la memoria.
Probar el flujo
Observa todo desde otra terminal:
mosquitto_sub -h broker.connectmq.com -p 1883 -i audit-2fa7c1 \
-t 'connectmq/demo/2fa7c1/door1/#' -v
Escanea una tarjeta desconocida: verás un evento access con "granted": false y el mismo payload en alert. Añade su hash a allowlist.json, envía al proceso un SIGHUP con kill -HUP <pid> y vuelve a escanear para ver "granted": true con el nombre.
Registro y alertas
El lector solo publica; la persistencia y la notificación las gestionan otros clientes MQTT, lo que mantiene sencillo el dispositivo. Las opciones habituales son:
- Un suscriptor en Python que escribe cada evento
accessen SQLite, como se muestra en guardar datos MQTT en SQLite con Python. - Una notificación por correo o chat en el topic
alertcon el patrón de alertas MQTT por correo con Python. - Un flujo de Node-RED que muestra el mensaje retained
lasten un dashboard. - Un cerradero eléctrico o una luz indicadora accionados por una segunda Pi, usando el controlador de relé MQTT con Raspberry Pi.
Más allá de la puerta principal
El mismo lector y el mismo formato de eventos sirven para otros trabajos. En un makerspace, un lector junto a una cortadora láser o un torno puede registrar quién usó la máquina y durante cuánto tiempo: publica un segundo evento cuando se retira la tarjeta y un suscriptor podrá calcular la duración de la sesión. Para el control de asistencia en un aula o una oficina pequeña, el topic retained last sirve también como pantalla en vivo de "quién acaba de llegar", y el registro en SQLite se convierte en una hoja de horas. En un armario de herramientas, una lectura puede desbloquear un cajón y registrar qué llavero lo abrió.
En cada caso solo cambian el nombre del lector y el prefijo del topic. Mantén un nivel de prefijo por ubicación, como site1/workshop/lathe, y un único suscriptor con un comodín + podrá recoger los eventos de todos los lectores. Un campo granted y un tag con hash estable es todo lo que necesitan las herramientas posteriores, lo que mantiene idéntico el código del dispositivo en todo el edificio.
Respuesta para quien escanea
La gente espera ver u oír que la lectura ha funcionado. Añade un LED verde y uno rojo, o un pequeño zumbador, en pines GPIO libres y acciónalos con GPIO.output() justo después de la decisión; ten en cuenta que la librería del lector ya pone RPi.GPIO en modo de numeración BOARD, así que usa los números de pin físicos. Da la respuesta en local en lugar de esperar un viaje de ida y vuelta por MQTT, para que siga siendo instantánea incluso en una red lenta.
Consideraciones de seguridad
Los proyectos RFID tocan la seguridad física, así que sé realista sobre sus límites. Las tarjetas MIFARE Classic básicas y muchos llaveros se pueden clonar, de modo que un sistema basado en UID es adecuado para registro, control de asistencia y acceso por comodidad, no para proteger zonas de alto valor. Además:
- Nunca ejecutes un sistema de acceso en el servidor MQTT público gratuito. Todo lo que hay en broker.connectmq.com es legible por cualquiera y está pensado solo para la primera prueba.
- Usa un broker privado con TLS y credenciales por dispositivo, para que nadie pueda publicar eventos falsos ni leer tus registros.
- Mantén
TAG_SECRETfuera del código y del control de versiones; guárdalo en un archivo de entorno de systemd conchmod 600. - Toma la decisión de acceso en local para que un corte de red no deje a la gente fuera ni abra la puerta.
Para una orientación más amplia, lee nuestras buenas prácticas de seguridad en MQTT.
Paso a producción
Un broker MQTT gestionado como ConnectMQ ofrece TLS en el puerto 8883, credenciales generadas vinculadas a cada client ID y un portal donde puedes ver llegar los eventos de acceso en el explorador de topics en vivo. Define las variables de entorno en tu archivo de servicio:
MQTT_HOST=broker.connectmq.com
MQTT_PORT=8883
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
MQTT_PREFIX=site1/building-a/door1
TAG_SECRET=a-long-random-secret
El tutorial de MQTT con Raspberry Pi incluye una plantilla de servicio systemd que puedes adaptar para que el lector arranque al encender.
Empieza con ConnectMQ
Dale a tus registros de acceso un camino privado y cifrado. Crea un broker Serverless gratuito en ConnectMQ, sin tarjeta de crédito, y conecta tu lector por TLS con las credenciales generadas. Es un broker MQTT en la nube al que tu registrador, tu dashboard y tus scripts de alertas pueden llegar sin abrir puertos en el sitio.
Preguntas frecuentes
¿Qué tarjetas RFID funcionan con el RC522?
El RC522 lee tarjetas ISO 14443A de 13,56 MHz como MIFARE Classic, MIFARE Ultralight y muchos llaveros. No lee tarjetas de proximidad de 125 kHz.
¿Por qué calcular un hash del UID RFID antes de publicarlo?
Un UID en bruto puede usarse para clonar tarjetas sencillas, y los mensajes MQTT suelen acabar en registros. Un hash con clave sigue identificando la tarjeta de forma única sin revelar el UID.
¿Funciona el RC522 en una Raspberry Pi 5?
Sí, con SPI activado y el paquete rpi-lgpio instalado en lugar de RPi.GPIO, que es lo que espera la librería mfrc522.
¿Qué pasa con las lecturas cuando la red está caída?
La Pi sigue decidiendo el acceso en local. Los eventos QoS 1 se guardan en memoria y se entregan tras la reconexión, siempre que el servicio siga en marcha.



