AccueilAteliersBadge RFID
ATELIER 07 Intermédiaire

Badge RFID

Un lecteur RC522 identifie les badges par leur numéro unique. D’abord la réaction immédiate — LED verte, LED rouge, buzzer — puis le vrai système : trois niveaux d’accès et une mémoire qui survit au redémarrage. Un seul câblage pour les deux.

3h

ESP32RFIDRC522SPISécurité

Ce que tu vas créer

Un système de contrôle d’accès par badge, en deux temps.

Partie 1 — le lecteur réagit. Tu passes un badge : LED verte et double bip aigu si l’accès est accordé, LED rouge et triple bip grave s’il est refusé. Tout est immédiat et physique, sans toucher au clavier.

Partie 2 — le vrai système. Le même montage devient un contrôle d’accès complet : enregistrement des badges avec nom et niveau (Visiteur, Membre, Admin), et mémorisation permanente — les badges survivent à un redémarrage.

🔌

Un seul câblage pour les deux parties

La partie 2 utilise exactement le même montage que la partie 1, en se servant simplement de moins de composants. Aucun fil à déplacer entre les deux.


Matériel nécessaire

ComposantQuantitéNotes
ESP321Fourni en atelier
Module RC5221Alimentation 3,3 V uniquement
Badges / cartes RFID3 à 513,56 MHz (Mifare)
LED verte + LED rouge2Avec une résistance 220 Ω chacune
Buzzer passif1Piloté en PWM
Câble micro-USB1Fourni en atelier

Connexions

⚠️

Le RC522 s’alimente en 3,3 V — jamais en 5 V

Le brancher sur la broche 5 V le détruit immédiatement et sans avertissement. C’est l’erreur la plus fréquente sur ce module : vérifie ce fil avant de brancher l’USB.

câblage
Module RC522 (bus SPI) :
SCK   ──── GPIO 18      horloge
MOSI  ──── GPIO 23      ESP32  →  RC522
MISO  ──── GPIO 19      RC522  →  ESP32
SDA   ──── GPIO 5       selection du composant (CS)
RST   ──── GPIO 22      reset
3.3V  ──── 3V3          JAMAIS sur 5V !
GND   ──── GND

Signalisation :
GPIO 2  ──── resistance 220 O ──── LED verte  ──── GND
GPIO 4  ──── resistance 220 O ──── LED rouge  ──── GND
GPIO 15 ──── buzzer ──── GND

Le module a sept broches et c’est le montage le plus dense de la série : prends ton temps, et relis chaque fil une fois le montage terminé. Pour le détail du composant, voir la fiche RC522.


Partie 1 — Le lecteur réagit

atelier8-rfid/main.py — Badge, LED et buzzer .python
atelier8-rfid/main.py
128 lignes GitHub
# Atelier 08 — Badge RFID (RC522)
# Fablab Ardèche — MicroPython
#
# Un lecteur RFID RC522 identifie les badges.
# Les badges autorisés allument la LED verte (accès accordé).
# Les badges inconnus déclenchent une alarme (LED rouge + buzzer).
#
# Avant de tester : lance le script enrolement() une fois
# pour lire les UIDs de tes badges et les mettre dans BADGES_AUTORISES.
#
# Matériel : ESP32, module RC522, 2 LEDs, buzzer
# Connexions :
#   RC522 SCK  → GPIO18
#   RC522 MOSI → GPIO23
#   RC522 MISO → GPIO19
#   RC522 SDA  → GPIO5  (Chip Select)
#   RC522 RST  → GPIO22
#   RC522 3.3V → 3.3V   (JAMAIS 5V !)
#   LED verte  → GPIO2  (+ résistance 220Ω)
#   LED rouge  → GPIO4  (+ résistance 220Ω)
#   Buzzer     → GPIO15

from machine import Pin, SPI, PWM
import time

# --- Badges autorisés ---
# Remplace ces UIDs par ceux de tes propres badges
# (utilise la fonction enrolement() ci-dessous pour les lire)
BADGES_AUTORISES = {
    # 'AA:BB:CC:DD': 'Prénom',
    # 'EE:FF:00:11': 'Autre personne',
}

# --- Initialisation ---
led_v = Pin(2,  Pin.OUT)
led_r = Pin(4,  Pin.OUT)
buz   = PWM(Pin(15))
buz.duty(0)

def bip(freq=1000, duree=100, nb=1):
    for _ in range(nb):
        buz.freq(freq)
        buz.duty(300)
        time.sleep_ms(duree)
        buz.duty(0)
        time.sleep_ms(60)

def acces_accorde(nom):
    print(f"✓ Accès accordé : {nom}")
    led_v.on()
    bip(1800, 120, 2)
    time.sleep_ms(1500)
    led_v.off()

def acces_refuse(uid_str):
    print(f"✗ Accès refusé  : {uid_str}")
    for _ in range(3):
        led_r.on()
        bip(400, 200)
        led_r.off()
        time.sleep_ms(100)

def uid_vers_str(uid):
    return ':'.join(f'{b:02X}' for b in uid)

# --- Lecture RFID ---
try:
    from mfrc522 import MFRC522
    spi  = SPI(1, baudrate=1_000_000,
               sck=Pin(18), mosi=Pin(23), miso=Pin(19))
    rfid = MFRC522(spi, gpioRst=22, gpioCs=5)
    rfid_ok = True
except ImportError:
    print("⚠ Module mfrc522 non installé.")
    print("  Lance : mpremote mip install mfrc522")
    rfid_ok = False

def enrolement():
    """Lance ce mode pour lire les UIDs de tes badges."""
    print("\n=== MODE ENRÔLEMENT ===")
    print("Présente tes badges un par un devant le lecteur.")
    print("Ctrl+C pour quitter.\n")
    vus = set()
    while True:
        stat, _ = rfid.request(rfid.REQIDL)
        if stat == rfid.OK:
            stat, uid = rfid.anticoll()
            if stat == rfid.OK:
                uid_str = uid_vers_str(uid)
                if uid_str not in vus:
                    vus.add(uid_str)
                    print(f"  Badge détecté : '{uid_str}'")
                    print(f"  → Ajoute dans BADGES_AUTORISES :")
                    print(f"    '{uid_str}': 'Prénom',\n")
                    bip(1200, 80)
        time.sleep_ms(200)

# --- Boucle principale ---
if not rfid_ok:
    print("Arrêt — installe le module mfrc522 d'abord.")
else:
    # Décommenter la ligne suivante pour lire les UIDs de tes badges :
    # enrolement()

    print("Lecteur RFID prêt.")
    if not BADGES_AUTORISES:
        print("⚠ Aucun badge enregistré — tous les badges seront refusés.")
        print("  Lance enrolement() pour enregistrer des badges.")

    # Clignotement de veille
    t0 = time.ticks_ms()
    while True:
        now = time.ticks_ms()
        led_v.value((now // 1000) % 2)  # clignote 1 fois par seconde

        stat, _ = rfid.request(rfid.REQIDL)
        if stat == rfid.OK:
            stat, uid = rfid.anticoll()
            if stat == rfid.OK:
                led_v.off()
                uid_str = uid_vers_str(uid)
                if uid_str in BADGES_AUTORISES:
                    acces_accorde(BADGES_AUTORISES[uid_str])
                else:
                    acces_refuse(uid_str)

        time.sleep_ms(100)
🤔 Avant de lancer Deux badges Mifare sortis du même paquet : comment le programme les distingue-t-il ? voir la réponse

Par leur UID — un numéro de série unique, gravé dans la puce à la fabrication et impossible à modifier.

C’est une suite d’octets (4 le plus souvent, parfois 7). La fonction uid_vers_str() la transforme en texte lisible du type A3:1F:9C:04, ce qui permet de l’utiliser comme clé dans le dictionnaire BADGES_AUTORISES.

Le badge ne contient donc aucune notion d’autorisation : il dit seulement « je suis le numéro A3:1F:9C:04 ». Toute la décision est prise par l’ESP32, en comparant ce numéro à sa liste.

Le bus SPI

Le RC522 dialogue en SPI, un protocole à quatre fils que l’on retrouve sur beaucoup de composants rapides :

FilRôle
SCKl’horloge — l’ESP32 donne le rythme
MOSIMaster Out, Slave In — l’ESP32 parle
MISOMaster In, Slave Out — le module répond
CSsélection — met ce composant à l’écoute

La broche CS (marquée SDA sur le module, ce qui prête à confusion) permet de brancher plusieurs composants SPI sur les mêmes trois fils : seul celui dont le CS est activé écoute. C’est ce qui distingue le SPI de l’I2C, où chaque composant possède une adresse.

Enregistrer tes propres badges

Le dictionnaire BADGES_AUTORISES est vide au départ — il faut y mettre tes badges. La fonction enrolement() est là pour ça : elle affiche l’UID de chaque badge présenté, que tu recopies ensuite dans le dictionnaire.

BADGES_AUTORISES = {
    'A3:1F:9C:04': 'Vincent',
    '7B:22:E0:91': 'Atelier',
}
🔧 À toi de jouer
  • Lance enrolement() et enregistre tes propres badges
  • Passe un badge inconnu : trois bips graves et LED rouge — c’est la voie du refus
  • Change les sons dans bip() : acces_accorde monte à 1800 Hz, acces_refuse descend à 400 Hz. Inverse-les pour entendre la différence
  • Allonge le temps d’accès : time.sleep_ms(1500)3000
  • Teste la portée : jusqu’à quelle distance le badge est-il encore lu ? À travers une feuille de papier ? une main ?
  • Plus dur : fais clignoter la LED verte pendant le délai d’accès, au lieu de la laisser fixe

Partie 2 — Le vrai système

Le même montage, mais le programme gère maintenant des niveaux d’accès et une mémoire permanente.

controle_acces_3niveaux.py — Contrôle d'accès à 3 niveaux .python
composants/rc522-rfid/controle_acces_3niveaux.py
284 lignes GitHub
# Contrôle d'accès RFID — 3 niveaux
# Fablab Ardèche — RC522 + ESP32
# -----------------------------------------------------------------------
# Deux modes :
#   1. PROGRAMMATION : enregistrer les badges avec Nom, Prénom et niveau
#   2. PRODUCTION : scanner les badges et autoriser ou refuser l'accès
#
# Niveaux d'accès :
#   1 = Visiteur   (accès basique)
#   2 = Membre     (accès intermédiaire)
#   3 = Admin      (accès total)
#
# Les badges sont sauvegardés dans badges.json sur l'ESP32.
# Ils persistent après un redémarrage.
#
# Câblage RC522 → ESP32 :
#   NSS→D5  SCK→D18  MOSI→D23  MISO→D19  RST→D22  3.3V→3.3V  GND→GND
# -----------------------------------------------------------------------

from machine import Pin, SPI
import time
import json

# --- Matériel ---
led = Pin(2, Pin.OUT)
led.off()

spi  = SPI(1, baudrate=1000000, sck=Pin(18), mosi=Pin(23), miso=Pin(19))

from mfrc522 import MFRC522
rfid = MFRC522(spi, gpioRst=22, gpioCs=5)

# --- Noms des niveaux ---
NIVEAUX = {
    1: "Visiteur",
    2: "Membre",
    3: "Admin"
}

# --- Fichier de sauvegarde ---
FICHIER_BADGES = "badges.json"


def charger_badges():
    """Charge la base de badges depuis le fichier JSON."""
    try:
        with open(FICHIER_BADGES, "r") as f:
            return json.load(f)
    except:
        return {}


def sauvegarder_badges(badges):
    """Sauvegarde la base de badges dans le fichier JSON."""
    with open(FICHIER_BADGES, "w") as f:
        json.dump(badges, f)


def uid_vers_str(uid):
    """Convertit un UID en texte lisible : 'AA:BB:CC:DD'."""
    return ':'.join(f'{b:02X}' for b in uid)


def lire_badge():
    """Attend qu'un badge soit présenté et retourne son UID (texte).
    Retourne None si Ctrl+C est pressé."""
    while True:
        stat, _ = rfid.request(rfid.REQIDL)
        if stat == rfid.OK:
            stat, uid = rfid.anticoll()
            if stat == rfid.OK:
                return uid_vers_str(uid)
        time.sleep_ms(200)


def flash_led(duree_ms=1500):
    """Allume la LED bleue pendant la durée indiquée."""
    led.on()
    time.sleep_ms(duree_ms)
    led.off()


# =======================================================================
# MODE PROGRAMMATION
# =======================================================================
def programmation():
    """Enregistrer des badges avec nom, prénom et niveau d'accès."""
    badges = charger_badges()

    print()
    print("=" * 45)
    print("  MODE PROGRAMMATION")
    print("=" * 45)
    print(f"  {len(badges)} badge(s) déjà enregistré(s)")
    print()
    print("  Niveaux : 1=Visiteur  2=Membre  3=Admin")
    print("  Ctrl+C pour revenir au menu")
    print("=" * 45)
    print()

    while True:
        print("Présentez un badge...")
        uid = lire_badge()

        # Vérifier si le badge existe déjà
        if uid in badges:
            info = badges[uid]
            print(f"  Badge déjà enregistré : {info['nom']}")
            print(f"  Niveau actuel : {info['niveau']} ({NIVEAUX[info['niveau']]})")
            rep = input("  Modifier ? (o/n) : ").strip().lower()
            if rep != 'o':
                print()
                time.sleep(1)  # attendre que le badge s'éloigne
                continue

        # Saisir le nom
        nom = input("  Nom et prénom : ").strip()
        if not nom:
            print("  Annulé.")
            print()
            continue

        # Saisir le niveau
        while True:
            try:
                niveau = int(input("  Niveau (1=Visiteur, 2=Membre, 3=Admin) : "))
                if niveau in (1, 2, 3):
                    break
            except ValueError:
                pass
            print("  → Tapez 1, 2 ou 3")

        # Enregistrer
        badges[uid] = {"nom": nom, "niveau": niveau}
        sauvegarder_badges(badges)

        print(f"  ✓ Badge enregistré : {nom}{NIVEAUX[niveau]}")
        print(f"    UID : {uid}")
        flash_led(500)
        print()

        time.sleep(1)  # attendre que le badge s'éloigne


# =======================================================================
# MODE PRODUCTION
# =======================================================================
def production(niveau_requis=1):
    """Contrôle d'accès : scanner les badges et vérifier le niveau.

    niveau_requis : niveau minimum pour que l'accès soit accordé
                    1 = tout le monde passe
                    2 = Membre et Admin seulement
                    3 = Admin seulement
    """
    badges = charger_badges()

    print()
    print("=" * 45)
    print("  MODE PRODUCTION")
    print("=" * 45)
    print(f"  {len(badges)} badge(s) en base")
    print(f"  Niveau requis : {niveau_requis} ({NIVEAUX.get(niveau_requis, '?')})")
    print("  Ctrl+C pour revenir au menu")
    print("=" * 45)
    print()

    while True:
        stat, _ = rfid.request(rfid.REQIDL)

        if stat == rfid.OK:
            stat, uid = rfid.anticoll()
            if stat == rfid.OK:
                uid_str = uid_vers_str(uid)

                if uid_str in badges:
                    info = badges[uid_str]
                    nom    = info["nom"]
                    niveau = info["niveau"]

                    if niveau >= niveau_requis:
                        # ACCÈS ACCORDÉ
                        print(f"  ✓ ACCÈS ACCORDÉ — {nom}")
                        print(f"    Niveau {niveau} ({NIVEAUX[niveau]})")
                        flash_led(1500)
                    else:
                        # ACCÈS REFUSÉ (niveau insuffisant)
                        print(f"  ✗ ACCÈS REFUSÉ — {nom}")
                        print(f"    Niveau {niveau} ({NIVEAUX[niveau]}) — requis : {niveau_requis} ({NIVEAUX[niveau_requis]})")
                else:
                    # BADGE INCONNU
                    print(f"  ✗ BADGE INCONNU — {uid_str}")

                print()
                time.sleep(1)  # attendre que le badge s'éloigne

        time.sleep_ms(200)


# =======================================================================
# MENU PRINCIPAL
# =======================================================================
def menu():
    """Menu principal — choix du mode."""
    while True:
        print()
        print("=" * 45)
        print("  CONTRÔLE D'ACCÈS RFID — 3 NIVEAUX")
        print("=" * 45)
        print()
        print("  1. Programmation (enregistrer des badges)")
        print("  2. Production — accès Visiteur+ (niveau 1)")
        print("  3. Production — accès Membre+   (niveau 2)")
        print("  4. Production — accès Admin     (niveau 3)")
        print("  5. Lister les badges enregistrés")
        print("  6. Supprimer un badge")
        print()

        choix = input("  Choix (1-6) : ").strip()

        try:
            if choix == '1':
                programmation()
            elif choix == '2':
                production(niveau_requis=1)
            elif choix == '3':
                production(niveau_requis=2)
            elif choix == '4':
                production(niveau_requis=3)
            elif choix == '5':
                lister_badges()
            elif choix == '6':
                supprimer_badge()
        except KeyboardInterrupt:
            print("\n  → Retour au menu")


def lister_badges():
    """Affiche tous les badges enregistrés."""
    badges = charger_badges()
    print()
    if not badges:
        print("  Aucun badge enregistré.")
        return

    print(f"  {len(badges)} badge(s) :")
    print(f"  {'UID':<20} {'Nom':<25} {'Niveau'}")
    print(f"  {'-'*20} {'-'*25} {'-'*15}")
    for uid, info in badges.items():
        n = info['niveau']
        print(f"  {uid:<20} {info['nom']:<25} {n} ({NIVEAUX[n]})")
    print()


def supprimer_badge():
    """Supprimer un badge par scan."""
    badges = charger_badges()
    if not badges:
        print("  Aucun badge enregistré.")
        return

    print()
    print("  Présentez le badge à supprimer...")
    uid = lire_badge()

    if uid in badges:
        info = badges[uid]
        print(f"  Badge trouvé : {info['nom']}{NIVEAUX[info['niveau']]}")
        rep = input("  Confirmer la suppression ? (o/n) : ").strip().lower()
        if rep == 'o':
            del badges[uid]
            sauvegarder_badges(badges)
            print("  ✓ Badge supprimé.")
        else:
            print("  Annulé.")
    else:
        print(f"  Badge {uid} non trouvé dans la base.")
    print()


# --- Lancement ---
print("Lecteur RFID RC522 initialisé.")
menu()

Le menu

Au lancement, le Shell propose : 1 programmer un badge · 2 à 4 lancer le contrôle avec un niveau minimum requis · 5 lister · 6 supprimer.

Les trois niveaux

Chaque badge reçoit un niveau : Visiteur (1), Membre (2) ou Admin (3). En production, on choisit le niveau minimum requis. Un Admin passe partout ; un Visiteur ne passe que si le niveau exigé est 1.

C’est le principe de toute la gestion de droits informatique — celui de ton propre ordinateur y compris.

La mémoire qui survit au redémarrage

Les badges sont écrits dans un fichier badges.json, sur l’ESP32 lui-même.

C’est la différence essentielle avec la partie 1, où la liste était écrite en dur dans le code : ici, débrancher la carte ne perd rien. Le format JSON est simplement du texte structuré, lisible tel quel :

{"A3:1F:9C:04": {"nom": "Vincent", "niveau": 3}}
🤔 Avant de lancer Les badges sont dans `badges.json`, sur la carte. Que se passe-t-il si tu réenvoies le programme depuis Thonny ? voir la réponse

Rien — les badges sont conservés.

badges.json et le programme sont deux fichiers distincts dans la mémoire flash de l’ESP32. Remplacer l’un ne touche pas l’autre.

C’est exactement ce qu’on attend d’un vrai système : mettre à jour le logiciel ne doit pas effacer les données. Pour repartir de zéro, il faut supprimer explicitement badges.json — depuis le gestionnaire de fichiers de Thonny, ou avec import os puis os.remove('badges.json').

🔧 À toi de jouer
  • Enregistre trois badges à trois niveaux différents, puis teste chaque niveau requis
  • Débranche la carte, rebranche-la, relance : tes badges sont toujours là
  • Regarde badges.json dans le gestionnaire de fichiers de Thonny (menu Affichage → Fichiers)
  • Les LED verte et rouge sont déjà câblées : fais-les servir ici aussi, comme en partie 1
  • Ajoute un 4ᵉ niveau, « Invité temporaire »
  • Plus dur : enregistre chaque passage avec l’heure dans un second fichier journal.json

Pour aller plus loin

Verrou magnétique 12V

Pilote un verrou électromagnétique via un relais pour créer une vraie porte à badge.

Comptage des passages

Enregistre chaque passage avec un horodatage — affiché sur OLED ou envoyé par WiFi.

Journalisation WiFi

Envoie chaque événement (badge + heure) vers un serveur ou une feuille Google Sheets.

Atelier précédent
Minuterie et séquenceur
Atelier suivant
Bientôt disponible