Raspberry Pi

Registro de accesos Raspberry Pi RFID MQTT con el lector RC522

Conecta un lector RFID RC522 a una Raspberry Pi, lee etiquetas en Python, compáralas con una lista blanca local y publica cada intento de acceso como un evento MQTT que pueden consumir registradores, dashboards y herramientas de alertas.

Lector RFID RC522 conectado a una Raspberry Pi junto a una puerta, con una tarjeta escaneada y un evento de acceso enviado a la nube en un proyecto Raspberry Pi RFID MQTT
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

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:

  1. Consulta el lector RC522 en busca de tarjetas y llaveros MIFARE de 13,56 MHz.
  2. Calcula un hash del UID de cada etiqueta para que el ID en bruto nunca salga del dispositivo.
  3. Busca el hash en un archivo local de lista blanca y decide si se concede o se deniega el acceso.
  4. Publica un evento de acceso por cada lectura y una alerta aparte para las etiquetas desconocidas.
  5. 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:

  1. Un suscriptor en Python que escribe cada evento access en SQLite, como se muestra en guardar datos MQTT en SQLite con Python.
  2. Una notificación por correo o chat en el topic alert con el patrón de alertas MQTT por correo con Python.
  3. Un flujo de Node-RED que muestra el mensaje retained last en un dashboard.
  4. 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_SECRET fuera del código y del control de versiones; guárdalo en un archivo de entorno de systemd con chmod 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.

Artículos relacionados

Fiabilidad de la Raspberry Pi: cómo usar una Pi 24/7 como gateway IoT

Una guía práctica para mantener un gateway MQTT con Raspberry Pi funcionando durante meses: desgaste del almacenamiento, sistemas de archivos de solo lectura, alimentación, refrigeración, watchdogs de hardware y software y monitorización remota de la salud.

9 min de lectura

Pico W vs Pico 2 W para MQTT: rendimiento, memoria y TLS de RP2040 frente a RP2350

La Pico 2 W mantiene el formato y el chip Wi-Fi de la Pico W, pero sustituye el RP2040 por el RP2350, más rápido. Esta comparativa analiza qué significa para los dispositivos MQTT: CPU, memoria, TLS, funciones de seguridad, soporte de software y qué placa comprar para cada proyecto.

9 min de lectura

Instalar Mosquitto en Raspberry Pi como broker MQTT local y seguro

Instala Mosquitto en una Raspberry Pi, protégelo con contraseñas y una configuración de listeners sensata, pruébalo con mosquitto_pub y mosquitto_sub, y aprende cuándo un broker local debe hacer de puente con un broker en la nube.

8 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.