Raspberry Pi

Raspberry Pi RFID MQTT Access Logging with the RC522 Reader

Wire an RC522 RFID reader to a Raspberry Pi, read tags in Python, check them against a local allowlist and publish every access attempt as an MQTT event that loggers, dashboards and alerting tools can consume.

RC522 RFID reader connected to a Raspberry Pi next to a door, with a card being scanned and an access event sent to the cloud in a raspberry pi rfid mqtt project
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

A Raspberry Pi RFID MQTT logger records every badge scan at a door, workshop machine or tool cabinet and sends it to any system that needs to know: a log database, a dashboard or an alerting script. In this tutorial you will wire an RC522 reader, read tags in Python, decide locally whether a tag is allowed, and publish structured access events over MQTT, including alerts for unknown cards.

What You Will Build

The Pi runs one Python service that:

  1. Polls the RC522 reader for 13.56 MHz MIFARE cards and key fobs.
  2. Hashes each tag's UID so the raw ID never leaves the device.
  3. Looks the hash up in a local allowlist file and decides granted or denied.
  4. Publishes an access event for every scan and a separate alert for unknown tags.
  5. Keeps a retained "last access" message and an online/offline status.

The access decision is made on the Pi, not in the cloud. That way the door keeps working when the network is down; MQTT is used for logging and notification, which is exactly what it is good at.

Wiring the RC522

The RC522 module talks SPI and runs on 3.3 V only. Connect it like this:

RC522 pin Raspberry Pi physical pin Function
SDA (SS) 24 SPI CE0
SCK 23 SPI clock
MOSI 19 SPI MOSI
MISO 21 SPI MISO
IRQ Not connected
GND 6 Ground
RST 22 GPIO25
3.3V 1 Power

Never connect the module to 5 V. Enable SPI with sudo raspi-config nonint do_spi 0 and reboot; afterwards ls /dev/spidev* should list /dev/spidev0.0.

Installing the Libraries

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

The mfrc522 package depends on RPi.GPIO, which does not work on the Raspberry Pi 5. On a Pi 5, replace it with the compatible rpi-lgpio package inside the virtual environment:

pip uninstall -y RPi.GPIO
pip install rpi-lgpio

Test the reader before adding MQTT:

python -c "from mfrc522 import SimpleMFRC522; r = SimpleMFRC522(); print('Hold a tag near the reader'); print(r.read_id())"

Hold a card or fob against the antenna and a long number should be printed.

Designing the Events

Topic Payload QoS Retained
connectmq/demo/2fa7c1/door1/access Access event JSON 1 No
connectmq/demo/2fa7c1/door1/alert Unknown tag JSON 1 No
connectmq/demo/2fa7c1/door1/last Most recent event 1 Yes
connectmq/demo/2fa7c1/door1/status online / offline 1 Yes

An access event looks like this:

{
  "reader": "door1",
  "tag": "a3f09b7c41de2280",
  "name": "Alice",
  "granted": true,
  "ts": 1792163300
}

The tag field is a keyed hash of the UID, not the UID itself. On a public broker, and in log files generally, a raw UID is something you should not spread around, because low-cost cards can be cloned from it.

The Allowlist File

Create allowlist.json in the project folder. You will fill in hashes after the first scans:

{
  "a3f09b7c41de2280": "Alice",
  "51c2e8d4097fab13": "Workshop spare fob"
}

To enrol a new card, scan it once, copy the tag value from the alert message, add it to the file with a name, and send the service a SIGHUP to reload, or simply restart it.

The Raspberry Pi RFID MQTT Script

Save as 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  # card is still resting on the reader
        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)
        # Drive a door strike or LED here with GPIO.output() if granted.
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()

The debounce stops a card lying on the reader from generating an event every tenth of a second. Because paho queues QoS 1 messages in memory while the connection is down, scans during a short network outage are delivered after reconnect; max_queued_messages_set caps that queue so a long outage cannot exhaust memory.

Testing the Flow

Watch everything from another terminal:

mosquitto_sub -h broker.connectmq.com -p 1883 -i audit-2fa7c1 \
  -t 'connectmq/demo/2fa7c1/door1/#' -v

Scan an unknown card: you will see an access event with "granted": false and the same payload on alert. Add its hash to allowlist.json, send the process a SIGHUP with kill -HUP <pid>, and scan again to see "granted": true with the name.

Logging and Alerting

The reader only publishes; persistence and notification are handled by other MQTT clients, which keeps the device simple. Common choices are:

  1. A Python subscriber that writes every access event into SQLite, as shown in saving MQTT data to SQLite with Python.
  2. An email or chat notification on the alert topic using the pattern in MQTT email alerts with Python.
  3. A Node-RED flow that displays the retained last message on a dashboard.
  4. A door strike or indicator light driven by a second Pi, using the Raspberry Pi relay MQTT controller.

Beyond the Front Door

The same reader and event format fit several other jobs. In a makerspace, a reader next to a laser cutter or lathe can log who used the machine and for how long: publish a second event when the card is removed, and a subscriber can compute session length. For attendance in a classroom or small office, the retained last topic doubles as a live "who just arrived" display, and the SQLite log becomes a timesheet. In a tool cabinet, a scan can unlock a drawer and record which fob opened it.

In each case, only the reader name and topic prefix change. Keep one prefix level per location, such as site1/workshop/lathe, and a single subscriber with a + wildcard can collect events from every reader. A granted field and a stable hashed tag are all the downstream tools need, which keeps the device code identical across the whole building.

Feedback for the Person Scanning

People expect to see or hear that a scan worked. Add a green and a red LED, or a small buzzer, on spare GPIO pins and drive them with GPIO.output() right after the decision; note that the reader library already puts RPi.GPIO in BOARD numbering mode, so use physical pin numbers. Give feedback locally rather than waiting for an MQTT round trip, so it stays instant even on a slow network.

Security Considerations

RFID projects touch physical security, so be realistic about their limits. Basic MIFARE Classic cards and many fobs can be cloned, so a UID-based system is suitable for logging, attendance and convenience access, not for protecting high-value areas. Beyond that:

  • Never run an access system on the public test broker. Everything at broker.connectmq.com is readable by anyone and is meant only for the first test.
  • Use a private broker with TLS and per-device credentials, so nobody can publish fake events or read your logs.
  • Keep TAG_SECRET out of the code and out of version control; store it in a systemd environment file with chmod 600.
  • Make the grant decision locally so a network outage does not lock people out or open the door.

For broader guidance, read our MQTT security best practices.

Moving to Production

A hosted MQTT broker such as ConnectMQ provides TLS on port 8883, generated credentials bound to each client ID, and a portal where you can watch access events arrive in the live topic explorer. Set the environment variables in your service file:

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

The Raspberry Pi MQTT tutorial includes a systemd service template you can adapt so the reader starts at boot.

Get Started with ConnectMQ

Give your access logs a private, encrypted path. Create a free Serverless broker on ConnectMQ, no credit card required, and connect your reader with the generated credentials over TLS. It is a cloud MQTT broker that your logger, dashboard and alerting scripts can all reach without opening ports at the site.

Frequently Asked Questions

Which RFID cards work with the RC522?

The RC522 reads 13.56 MHz ISO 14443A cards such as MIFARE Classic, MIFARE Ultralight and many key fobs. It does not read 125 kHz proximity cards.

Why hash the RFID UID before publishing it?

A raw UID can be used to clone simple cards, and MQTT messages often end up in logs. A keyed hash still identifies the card uniquely without revealing the UID.

Does the RC522 work on a Raspberry Pi 5?

Yes, with SPI enabled and the rpi-lgpio package installed in place of RPi.GPIO, which the mfrc522 library expects.

What happens to scans when the network is down?

The Pi still decides access locally. QoS 1 events are queued in memory and delivered after reconnect, as long as the service keeps running.

Related articles

Pico W vs Pico 2 W for MQTT: RP2040 vs RP2350 Performance, Memory and TLS

The Pico 2 W keeps the Pico W's footprint and Wi-Fi chip but swaps the RP2040 for the faster RP2350. This comparison looks at what that means for MQTT devices: CPU, memory, TLS, security features, software support, and which board to buy for which project.

8 min read

Raspberry Pi BLE to MQTT Bridge with Python and Bleak

Use the Raspberry Pi's built-in Bluetooth, the bleak library and paho-mqtt to scan BLE sensor advertisements, decode temperature and humidity, and publish throttled readings to MQTT.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.