Raspberry Pi

Journal d'accès RFID MQTT sur Raspberry Pi avec le lecteur RC522

Câblez un lecteur RFID RC522 sur un Raspberry Pi, lisez les badges en Python, vérifiez-les dans une liste blanche locale et publiez chaque tentative d'accès sous forme d'événement MQTT exploitable par vos outils de journalisation, tableaux de bord et alertes.

Lecteur RFID RC522 relié à un Raspberry Pi près d'une porte, un badge étant scanné et un événement d'accès envoyé vers le cloud via MQTT
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

Un journal d'accès Raspberry Pi RFID MQTT enregistre chaque passage de badge à une porte, une machine d'atelier ou une armoire à outils, et l'envoie à tout système qui doit en être informé : une base de journalisation, un tableau de bord ou un script d'alerte. Dans ce tutoriel, vous allez câbler un lecteur RC522, lire les badges en Python, décider localement si un badge est autorisé, et publier des événements d'accès structurés via MQTT, y compris des alertes pour les cartes inconnues.

Ce que vous allez construire

Le Pi exécute un service Python unique qui :

  1. Interroge le lecteur RC522 à la recherche de cartes et de porte-clés MIFARE 13,56 MHz.
  2. Hache l'UID de chaque badge pour que l'identifiant brut ne quitte jamais l'appareil.
  3. Recherche ce hachage dans un fichier de liste blanche local et décide si l'accès est accordé ou refusé.
  4. Publie un événement d'accès pour chaque passage et une alerte séparée pour les badges inconnus.
  5. Conserve un message retained « dernier accès » ainsi qu'un statut en ligne/hors ligne.

La décision d'accès est prise sur le Pi, pas dans le cloud. Ainsi, la porte continue de fonctionner quand le réseau est coupé ; MQTT sert à la journalisation et à la notification, ce pour quoi il excelle.

Câbler le RC522

Le module RC522 communique en SPI et fonctionne uniquement en 3,3 V. Raccordez-le ainsi :

Broche RC522 Broche physique Raspberry Pi Fonction
SDA (SS) 24 SPI CE0
SCK 23 Horloge SPI
MOSI 19 SPI MOSI
MISO 21 SPI MISO
IRQ Non raccordée
GND 6 Masse
RST 22 GPIO25
3.3V 1 Alimentation

Ne branchez jamais le module sur du 5 V. Activez le SPI avec sudo raspi-config nonint do_spi 0 et redémarrez ; ensuite, ls /dev/spidev* doit afficher /dev/spidev0.0.

Installer les bibliothèques

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

Le paquet mfrc522 dépend de RPi.GPIO, qui ne fonctionne pas sur le Raspberry Pi 5. Sur un Pi 5, remplacez-le par le paquet compatible rpi-lgpio dans l'environnement virtuel :

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

Testez le lecteur avant d'ajouter MQTT :

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

Approchez une carte ou un porte-clés de l'antenne : un long nombre doit s'afficher.

Concevoir les événements

Topic Payload QoS Retained
connectmq/demo/2fa7c1/door1/access JSON de l'événement d'accès 1 Non
connectmq/demo/2fa7c1/door1/alert JSON du badge inconnu 1 Non
connectmq/demo/2fa7c1/door1/last Événement le plus récent 1 Oui
connectmq/demo/2fa7c1/door1/status online / offline 1 Oui

Un événement d'accès ressemble à ceci :

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

Le champ tag est un hachage à clé de l'UID, pas l'UID lui-même. Sur un broker public, et dans les fichiers journaux en général, un UID brut ne doit pas circuler, car les cartes bon marché peuvent être clonées à partir de lui.

Le fichier de liste blanche

Créez allowlist.json dans le dossier du projet. Vous y ajouterez les hachages après les premiers passages :

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

Pour enregistrer une nouvelle carte, passez-la une fois, copiez la valeur tag du message d'alerte, ajoutez-la au fichier avec un nom, puis envoyez un SIGHUP au service pour qu'il recharge la liste, ou redémarrez-le simplement.

Le script Raspberry Pi RFID MQTT

Enregistrez-le sous 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 carte est toujours posée sur le lecteur
        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)
        # Commandez ici une gâche ou une LED avec GPIO.output() si l'accès est accordé.
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()

L'anti-rebond empêche une carte posée sur le lecteur de générer un événement tous les dixièmes de seconde. Comme paho met en file d'attente en mémoire les messages QoS 1 pendant que la connexion est coupée, les passages survenus lors d'une courte panne réseau sont livrés après la reconnexion ; max_queued_messages_set plafonne cette file pour qu'une longue coupure ne puisse pas épuiser la mémoire.

Tester le flux

Observez tout depuis un autre terminal :

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

Passez une carte inconnue : vous verrez un événement access avec "granted": false et le même payload sur alert. Ajoutez son hachage à allowlist.json, envoyez un SIGHUP au processus avec kill -HUP <pid>, puis repassez la carte pour voir "granted": true avec le nom.

Journalisation et alertes

Le lecteur ne fait que publier ; la persistance et la notification sont assurées par d'autres clients MQTT, ce qui garde l'appareil simple. Les choix courants sont :

  1. Un abonné Python qui écrit chaque événement access dans SQLite, comme dans enregistrer des données MQTT dans SQLite avec Python.
  2. Une notification par e-mail ou messagerie sur le topic alert, selon le modèle présenté dans alertes e-mail MQTT avec Python.
  3. Un flux Node-RED qui affiche le message retained last sur un tableau de bord.
  4. Une gâche électrique ou un voyant piloté par un second Pi, à l'aide du contrôleur relais Raspberry Pi MQTT.

Au-delà de la porte d'entrée

Le même lecteur et le même format d'événement conviennent à plusieurs autres usages. Dans un fablab, un lecteur placé à côté d'une découpeuse laser ou d'un tour peut enregistrer qui a utilisé la machine et pendant combien de temps : publiez un second événement au retrait de la carte, et un abonné peut calculer la durée de la session. Pour le pointage dans une salle de classe ou un petit bureau, le topic retained last sert aussi d'affichage en direct « qui vient d'arriver », et le journal SQLite devient une feuille de temps. Dans une armoire à outils, un passage de badge peut déverrouiller un tiroir et enregistrer quel porte-clés l'a ouvert.

Dans chaque cas, seuls le nom du lecteur et le préfixe de topic changent. Gardez un niveau de préfixe par emplacement, comme site1/workshop/lathe, et un seul abonné avec le joker + peut collecter les événements de tous les lecteurs. Un champ granted et un tag haché stable suffisent aux outils en aval, ce qui permet de garder un code identique sur tous les appareils du bâtiment.

Un retour pour la personne qui badge

Les gens s'attendent à voir ou entendre que leur passage a fonctionné. Ajoutez une LED verte et une LED rouge, ou un petit buzzer, sur des broches GPIO libres et commandez-les avec GPIO.output() juste après la décision ; notez que la bibliothèque du lecteur place déjà RPi.GPIO en mode de numérotation BOARD, utilisez donc les numéros de broches physiques. Donnez ce retour localement plutôt que d'attendre un aller-retour MQTT, afin qu'il reste instantané même sur un réseau lent.

Considérations de sécurité

Les projets RFID touchent à la sécurité physique : soyez réaliste sur leurs limites. Les cartes MIFARE Classic de base et de nombreux porte-clés peuvent être clonés ; un système fondé sur l'UID convient donc à la journalisation, au pointage et aux accès de confort, pas à la protection de zones sensibles. Par ailleurs :

  • Ne faites jamais tourner un système d'accès sur le serveur MQTT public gratuit. Tout ce qui transite par broker.connectmq.com est lisible par n'importe qui et n'est destiné qu'au premier test.
  • Utilisez un broker privé avec TLS et des identifiants par appareil, pour que personne ne puisse publier de faux événements ni lire vos journaux.
  • Gardez TAG_SECRET hors du code et hors du contrôle de version ; stockez-le dans un fichier d'environnement systemd avec chmod 600.
  • Prenez la décision d'accès localement pour qu'une panne réseau n'enferme personne dehors et n'ouvre pas la porte.

Pour des recommandations plus larges, lisez nos bonnes pratiques de sécurité MQTT.

Passer en production

Un broker MQTT managé comme ConnectMQ fournit TLS sur le port 8883, des identifiants générés liés à chaque client ID, et un portail où vous pouvez voir les événements d'accès arriver dans l'explorateur de topics en direct. Définissez les variables d'environnement dans votre fichier de service :

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

Le tutoriel MQTT sur Raspberry Pi inclut un modèle de service systemd que vous pouvez adapter pour que le lecteur démarre au boot.

Démarrer avec ConnectMQ

Offrez à vos journaux d'accès un chemin privé et chiffré. Créez un broker Serverless gratuit sur ConnectMQ, sans carte bancaire, et connectez votre lecteur en TLS avec les identifiants générés. C'est un broker MQTT cloud que votre journaliseur, votre tableau de bord et vos scripts d'alerte peuvent tous joindre sans ouvrir de ports sur le site.

Questions fréquentes

Quelles cartes RFID fonctionnent avec le RC522 ?

Le RC522 lit les cartes ISO 14443A 13,56 MHz comme les MIFARE Classic, MIFARE Ultralight et de nombreux porte-clés. Il ne lit pas les cartes de proximité 125 kHz.

Pourquoi hacher l'UID RFID avant de le publier ?

Un UID brut peut servir à cloner des cartes simples, et les messages MQTT finissent souvent dans des journaux. Un hachage à clé identifie toujours la carte de manière unique sans révéler l'UID.

Le RC522 fonctionne-t-il sur un Raspberry Pi 5 ?

Oui, avec le SPI activé et le paquet rpi-lgpio installé à la place de RPi.GPIO, attendu par la bibliothèque mfrc522.

Que deviennent les passages de badge quand le réseau est coupé ?

Le Pi continue de décider l'accès localement. Les événements QoS 1 sont mis en file d'attente en mémoire et livrés après la reconnexion, tant que le service continue de tourner.

Articles liés

Installer Mosquitto sur Raspberry Pi comme broker MQTT local sécurisé

Installez Mosquitto sur un Raspberry Pi, sécurisez-le avec des mots de passe et une configuration de listener raisonnable, testez-le avec mosquitto_pub et mosquitto_sub, et découvrez quand un broker local doit faire un pont vers un broker cloud.

9 min de lecture

Pont BLE vers MQTT sur Raspberry Pi avec Python et Bleak

Utilisez le Bluetooth intégré du Raspberry Pi, la bibliothèque bleak et paho-mqtt pour scanner les annonces de capteurs BLE, décoder température et humidité, et publier des mesures limitées en fréquence vers MQTT.

8 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.