Um logger Raspberry Pi RFID MQTT registra cada leitura de crachá em uma porta, máquina de oficina ou armário de ferramentas e a envia para qualquer sistema que precise saber: um banco de dados de logs, um dashboard ou um script de alertas. Neste tutorial você vai ligar um leitor RC522, ler tags em Python, decidir localmente se uma tag é permitida e publicar eventos de acesso estruturados via MQTT, incluindo alertas para cartões desconhecidos.
O que você vai construir
O Pi roda um único serviço Python que:
- Consulta o leitor RC522 em busca de cartões MIFARE e chaveiros de 13,56 MHz.
- Gera um hash do UID de cada tag para que o ID bruto nunca saia do dispositivo.
- Procura o hash em um arquivo local de lista de permissões e decide entre liberado ou negado.
- Publica um evento de acesso a cada leitura e um alerta separado para tags desconhecidas.
- Mantém uma mensagem retained de "último acesso" e um status online/offline.
A decisão de acesso é tomada no Pi, não na nuvem. Assim a porta continua funcionando quando a rede cai; o MQTT é usado para registro e notificação, que é exatamente aquilo em que ele é bom.
Ligando o RC522
O módulo RC522 fala SPI e funciona apenas com 3,3 V. Ligue assim:
| Pino do RC522 | Pino físico do Raspberry Pi | Função |
|---|---|---|
| SDA (SS) | 24 | SPI CE0 |
| SCK | 23 | Clock SPI |
| MOSI | 19 | SPI MOSI |
| MISO | 21 | SPI MISO |
| IRQ | Não conectado | |
| GND | 6 | Terra |
| RST | 22 | GPIO25 |
| 3.3V | 1 | Alimentação |
Nunca ligue o módulo em 5 V. Ative o SPI com sudo raspi-config nonint do_spi 0 e reinicie; depois, ls /dev/spidev* deve listar /dev/spidev0.0.
Instalando as bibliotecas
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
O pacote mfrc522 depende do RPi.GPIO, que não funciona no Raspberry Pi 5. Em um Pi 5, substitua-o pelo pacote compatível rpi-lgpio dentro do ambiente virtual:
pip uninstall -y RPi.GPIO
pip install rpi-lgpio
Teste o leitor antes de adicionar o MQTT:
python -c "from mfrc522 import SimpleMFRC522; r = SimpleMFRC522(); print('Hold a tag near the reader'); print(r.read_id())"
Aproxime um cartão ou chaveiro da antena e um número longo deve ser impresso.
Desenhando os eventos
| Topic | Payload | QoS | Retained |
|---|---|---|---|
connectmq/demo/2fa7c1/door1/access |
JSON do evento de acesso | 1 | Não |
connectmq/demo/2fa7c1/door1/alert |
JSON da tag desconhecida | 1 | Não |
connectmq/demo/2fa7c1/door1/last |
Evento mais recente | 1 | Sim |
connectmq/demo/2fa7c1/door1/status |
online / offline |
1 | Sim |
Um evento de acesso tem esta cara:
{
"reader": "door1",
"tag": "a3f09b7c41de2280",
"name": "Alice",
"granted": true,
"ts": 1792163300
}
O campo tag é um hash com chave do UID, não o UID em si. Em um broker público, e em arquivos de log de modo geral, um UID bruto é algo que você não deve espalhar, porque cartões baratos podem ser clonados a partir dele.
O arquivo de lista de permissões
Crie allowlist.json na pasta do projeto. Você vai preencher os hashes depois das primeiras leituras:
{
"a3f09b7c41de2280": "Alice",
"51c2e8d4097fab13": "Workshop spare fob"
}
Para cadastrar um cartão novo, leia-o uma vez, copie o valor de tag da mensagem de alerta, adicione-o ao arquivo com um nome e envie ao serviço um SIGHUP para recarregar, ou simplesmente reinicie-o.
O script Raspberry Pi RFID MQTT
Salve 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 # o cartão continua apoiado no leitor
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)
# Acione aqui uma fechadura elétrica ou LED com GPIO.output() se liberado.
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()
O debounce impede que um cartão apoiado no leitor gere um evento a cada décimo de segundo. Como a paho enfileira mensagens QoS 1 na memória enquanto a conexão está caída, as leituras feitas durante uma queda curta de rede são entregues após a reconexão; max_queued_messages_set limita essa fila para que uma queda longa não esgote a memória.
Testando o fluxo
Acompanhe tudo a partir de outro terminal:
mosquitto_sub -h broker.connectmq.com -p 1883 -i audit-2fa7c1 \
-t 'connectmq/demo/2fa7c1/door1/#' -v
Leia um cartão desconhecido: você verá um evento access com "granted": false e o mesmo payload em alert. Adicione o hash dele ao allowlist.json, envie um SIGHUP ao processo com kill -HUP <pid> e leia de novo para ver "granted": true com o nome.
Registro e alertas
O leitor apenas publica; a persistência e as notificações ficam a cargo de outros clientes MQTT, o que mantém o dispositivo simples. As escolhas mais comuns são:
- Um assinante Python que grava cada evento
accessno SQLite, como mostrado em como salvar dados MQTT no SQLite com Python. - Uma notificação por e-mail ou chat no topic
alert, usando o padrão de alertas MQTT por e-mail com Python. - Um fluxo Node-RED que exibe a mensagem retained
lastem um dashboard. - Uma fechadura elétrica ou luz indicadora acionada por um segundo Pi, usando o controlador de relé no Raspberry Pi via MQTT.
Além da porta de entrada
O mesmo leitor e o mesmo formato de evento servem para vários outros trabalhos. Em um makerspace, um leitor ao lado de uma cortadora a laser ou de um torno pode registrar quem usou a máquina e por quanto tempo: publique um segundo evento quando o cartão for retirado, e um assinante pode calcular a duração da sessão. Para controle de presença em uma sala de aula ou em um pequeno escritório, o topic retained last funciona também como um painel ao vivo de "quem acabou de chegar", e o log no SQLite vira uma folha de ponto. Em um armário de ferramentas, uma leitura pode destravar uma gaveta e registrar qual chaveiro a abriu.
Em cada caso, só mudam o nome do leitor e o prefixo de topic. Mantenha um nível de prefixo por local, como site1/workshop/lathe, e um único assinante com o curinga + consegue coletar eventos de todos os leitores. Um campo granted e uma tag com hash estável são tudo de que as ferramentas a jusante precisam, o que mantém o código do dispositivo idêntico em todo o prédio.
Retorno para quem faz a leitura
As pessoas esperam ver ou ouvir que a leitura funcionou. Adicione um LED verde e um vermelho, ou um pequeno buzzer, em pinos GPIO livres e acione-os com GPIO.output() logo após a decisão; observe que a biblioteca do leitor já coloca o RPi.GPIO no modo de numeração BOARD, então use os números dos pinos físicos. Dê o retorno localmente em vez de esperar uma ida e volta pelo MQTT, para que ele continue instantâneo mesmo em uma rede lenta.
Considerações de segurança
Projetos com RFID tocam na segurança física, então seja realista sobre os limites deles. Cartões MIFARE Classic básicos e muitos chaveiros podem ser clonados, então um sistema baseado em UID é adequado para registro, controle de presença e acesso por conveniência, não para proteger áreas de alto valor. Além disso:
- Nunca rode um sistema de acesso no servidor de testes público. Tudo no servidor MQTT público em broker.connectmq.com pode ser lido por qualquer pessoa e serve apenas para o primeiro teste.
- Use um broker privado com TLS e credenciais por dispositivo, para que ninguém consiga publicar eventos falsos ou ler os seus logs.
- Mantenha o
TAG_SECRETfora do código e fora do controle de versão; guarde-o em um arquivo de ambiente do systemd comchmod 600. - Tome a decisão de liberação localmente, para que uma queda de rede não tranque as pessoas do lado de fora nem abra a porta.
- Logs de acesso associados a nomes de pessoas são dados pessoais; no Brasil, trate-os de acordo com a LGPD, com retenção e acesso definidos.
Para orientações mais amplas, leia nossas boas práticas de segurança MQTT.
Indo para a produção
Um broker MQTT gerenciado como o ConnectMQ oferece TLS na porta 8883, credenciais geradas e vinculadas a cada client ID e um portal onde você pode acompanhar a chegada dos eventos de acesso no explorador de topics ao vivo. Defina as variáveis de ambiente no seu arquivo de serviço:
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
O tutorial MQTT com Raspberry Pi inclui um modelo de serviço do systemd que você pode adaptar para que o leitor inicie no boot.
Comece com o ConnectMQ
Dê aos seus logs de acesso um caminho privado e criptografado. Crie um broker Serverless gratuito no ConnectMQ, sem cartão de crédito, e conecte o seu leitor com as credenciais geradas via TLS. É um broker MQTT na nuvem que o seu logger, o seu dashboard e os seus scripts de alerta conseguem acessar sem abrir portas no local.
Perguntas frequentes
Quais cartões RFID funcionam com o RC522?
O RC522 lê cartões ISO 14443A de 13,56 MHz, como MIFARE Classic, MIFARE Ultralight e muitos chaveiros. Ele não lê cartões de proximidade de 125 kHz.
Por que gerar um hash do UID RFID antes de publicá-lo?
Um UID bruto pode ser usado para clonar cartões simples, e mensagens MQTT muitas vezes acabam em logs. Um hash com chave continua identificando o cartão de forma única sem revelar o UID.
O RC522 funciona no Raspberry Pi 5?
Sim, com o SPI ativado e o pacote rpi-lgpio instalado no lugar do RPi.GPIO, que a biblioteca mfrc522 espera.
O que acontece com as leituras quando a rede cai?
O Pi continua decidindo o acesso localmente. Os eventos QoS 1 ficam enfileirados na memória e são entregues após a reconexão, desde que o serviço continue rodando.



