Projeto completo: Controle de Acesso RFID
(ESP32 + WebSocket + React)
Este repositório contém três partes:
• server/ — WebSocket server em [Link] (Express + ws) e REST simples para gerenciar UIDs
autorizados.
• frontend/ — App React (Vite) que mostra notificações de leitura e permite liberar a porta.
• esp32/ — Código Arduino (ESP32) para leitor RC522 que se conecta ao servidor via WebSocket.
Árvore de arquivos
acesso-rfid-project/
├─ server/
│ ├─ [Link]
│ ├─ [Link]
│ ├─ .[Link]
│ └─ [Link]
├─ frontend/
│ ├─ [Link]
│ ├─ [Link]
│ └─ src/
│ ├─ [Link]
│ ├─ [Link]
│ └─ [Link]
└─ esp32/
└─ esp32_rfid_ws.ino
server/[Link]
{
"name": "acesso-rfid-server",
"version": "1.0.0",
"main": "[Link]",
"scripts": {
"start": "node [Link]"
},
"dependencies": {
"express": "^4.18.2",
"ws": "^8.13.0",
"body-parser": "^1.20.2",
"dotenv": "^16.0.3"
1
}
}
server/.[Link]
PORT=3000
AUTHORIZED_UIDS=A1:B2:C3:D4
server/[Link]
require('dotenv').config();
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const bodyParser = require('body-parser');
const app = express();
[Link]([Link]());
// Config
const PORT = [Link] || 3000;
const initial = ([Link].AUTHORIZED_UIDS || '').split(',').map(s =>
[Link]()).filter(Boolean);
// Memory stores (replace with DB in produção)
const authorized = new Set(initial);
const connectedESP = new Map(); // deviceId -> ws
const webClients = new Set();
const server = [Link](app);
const wss = new [Link]({ server });
[Link]('connection', function connection(ws, req) {
[Link]('[WS] connected');
[Link]('message', function incoming(message) {
try {
const msg = [Link]([Link]());
handleMessage(ws, msg);
} catch (e) {
[Link]('[WS] invalid message', e);
}
});
2
[Link]('close', () => {
for (const [id, socket] of [Link]()) {
if (socket === ws) [Link](id);
}
[Link](ws);
});
});
function handleMessage(ws, msg) {
if (!msg || ![Link]) return;
if ([Link] === 'hello' && [Link]) {
[Link]([Link], ws);
[Link] = [Link];
[Link]('[WS] ESP connected:', [Link]);
return;
}
if ([Link] === 'scan' && [Link] && [Link]) {
const payload = {
type: 'card_scanned',
uid: [Link],
deviceId: [Link],
time: new Date().toISOString(),
authorized: [Link]([Link])
};
for (const client of webClients) {
if ([Link] === [Link])
[Link]([Link](payload));
}
[Link]('[WS] scan', [Link], 'authorized?',
[Link]([Link]));
return;
}
if ([Link] === 'register_web') {
[Link](ws);
[Link] = true;
[Link]('[WS] web client registered');
return;
}
if ([Link] === 'grant_request' && [Link] && [Link]) {
const deviceWs = [Link]([Link]);
if (!deviceWs) {
if (ws && [Link] && [Link] === [Link])
[Link]([Link]({ type: 'error', message: 'device offline' }));
return;
}
const grant = { type: 'grant', uid: [Link], duration: [Link] ||
3000 };
3
[Link]([Link](grant));
for (const client of webClients) {
if ([Link] === [Link]) [Link]([Link]({
type: 'grant_event', uid: [Link], deviceId: [Link], by: 'web' }));
}
[Link]('[WS] grant sent for', [Link], 'to', [Link]);
return;
}
}
// Simple REST API to view and manage authorized UIDs
[Link]('/api/authorized', (req, res) => {
[Link]([Link](authorized));
});
[Link]('/api/authorized', (req, res) => {
const { uid } = [Link];
if (!uid) return [Link](400).json({ error: 'uid required' });
[Link](uid);
[Link]({ ok: true });
});
[Link]('/api/authorized', (req, res) => {
const { uid } = [Link];
[Link](uid);
[Link]({ ok: true });
});
[Link](PORT, () => [Link]('Server listening on', PORT));
server/[Link]
# Server WebSocket - Acesso RFID
Instalação:
```bash
cd server
npm install
cp .[Link] .env
# ajustar .env
npm start
O servidor escuta na porta configurada ( PORT ).
Endpoints REST: /api/authorized (GET, POST, DELETE) para gerenciar UIDs.
WebSocket: [Link] — o servidor aceita mensagens JSON conforme protocolo.
4
---
# frontend/[Link]
```json
{
"name": "acesso-rfid-frontend",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"vite": "5.0.0"
}
}
frontend/[Link]
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Acesso RFID</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/[Link]"></script>
</body>
</html>
frontend/src/[Link]
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
5
import './[Link]'
createRoot([Link]('root')).render(<App />)
frontend/src/[Link]
import React, { useEffect, useState } from 'react'
const WS_URL = [Link].VITE_WS_URL || '[Link]
export default function App(){
const [ws, setWs] = useState(null);
const [events, setEvents] = useState([]);
useEffect(() => {
const socket = new WebSocket(WS_URL);
[Link]('open', () => {
[Link]([Link]({ type: 'register_web' }));
});
[Link]('message', e => {
try{
const msg = [Link]([Link]);
if ([Link] === 'card_scanned') setEvents(ev => [msg, ...ev]);
else [Link]('ws msg', msg);
}catch(err){ [Link](err) }
});
setWs(socket);
return () => [Link]();
}, []);
function handleLiberar(ev){
if (!ws || [Link] !== [Link]) return alert('Conexão WS
fechada');
[Link]([Link]({ type: 'grant_request', uid: [Link], deviceId:
[Link], duration: 3000 }));
}
return (
<div style={{ padding: 20, fontFamily: 'Arial, sans-serif' }}>
<h1>Controle de Acesso (WS)</h1>
<div>
{[Link] === 0 && <div>Nenhuma leitura ainda</div>}
{[Link]((ev, i) => (
<div key={i} style={{ border: '1px solid #ddd', padding: 10,
marginBottom: 8 }}>
<div><b>UID:</b> {[Link]}</div>
<div><b>Device:</b> {[Link]}</div>
<div><b>Hora:</b> {new Date([Link]).toLocaleString()}</div>
6
<div><b>Autorizado:</b> {[Link] ? 'SIM' : 'NÃO'}</div>
<button onClick={() => handleLiberar(ev)} style={{ marginTop:
6 }}>Liberar</button>
</div>
))}
</div>
</div>
)
}
frontend/src/[Link]
body { margin: 0; padding: 0; }
esp32/esp32_rfid_ws.ino
#include <SPI.h>
#include <MFRC522.h>
#include <WiFi.h>
#include <WebSocketsClient.h>
#define RST_PIN 22
#define SS_PIN 21
MFRC522 rfid(SS_PIN, RST_PIN);
const char* ssid = "SEU_SSID";
const char* pass = "SUA_SENHA";
const char* wsHost = "SEU_SERVIDOR_IP"; // ex: [Link]
uint16_t wsPort = 3000;
WebSocketsClient webSocket;
void webEvent(WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_CONNECTED:
[Link]("[WS] connected");
[Link]("{\"type\":\"hello\",\"deviceId\":\"esp01\"}");
break;
case WStype_TEXT:
{
String msg = String((char*)payload);
[Link]("[WS] msg: " + msg);
if ([Link]("\"type\":\"grant\"") >= 0) {
int dpos = [Link]("\"duration\":");
7
int duration = 3000;
if (dpos > 0) {
int start = dpos + 11;
int end = [Link]('}', start);
if (end > start) duration = [Link](start, end).toInt();
}
openDoor(duration);
}
}
break;
default:
break;
}
}
void openDoor(int ms) {
[Link]("Abrindo porta por ms: " + String(ms));
digitalWrite(2, HIGH);
delay(ms);
digitalWrite(2, LOW);
}
void setup() {
[Link](115200);
[Link]();
rfid.PCD_Init();
pinMode(2, OUTPUT);
digitalWrite(2, LOW);
[Link](ssid, pass);
[Link]("Conectando WiFi");
while ([Link]() != WL_CONNECTED) {
delay(500);
[Link](".");
}
[Link]("\nWiFi ok");
[Link](wsHost, wsPort, "/");
[Link](webEvent);
[Link](5000);
}
void loop() {
[Link]();
if ( ! rfid.PICC_IsNewCardPresent()) return;
if ( ! rfid.PICC_ReadCardSerial()) return;
String uid = "";
for (byte i = 0; i < [Link]; i++) {
if (i > 0) uid += ":";
if ([Link][i] < 0x10) uid += "0";
8
uid += String([Link][i], HEX);
}
[Link]();
[Link]("Leu UID: " + uid);
String payload = "{\"type\":\"scan\",\"uid\":\"" + uid + "\",\"deviceId\":
\"esp01\"}";
[Link](payload);
delay(1000);
}
Como usar (resumo rápido)
1. Server:
2. cd server
3. npm install
4. copiar .[Link] para .env e ajustar PORT e AUTHORIZED_UIDS (ex:
A1:B2:C3:D4 )
5. npm start
6. Frontend:
7. cd frontend
8. npm install
9. ajustar VITE_WS_URL se necessário (ou editar src/[Link] para apontar para o servidor)
10. npm run dev
11. ESP32:
12. abrir esp32/esp32_rfid_ws.ino
13. ajustar ssid , pass e wsHost para o IP do servidor
14. compilar e enviar para a placa
Observações e próximos passos recomendados
• Em produção, substitua o armazenamento em memória por um banco (SQLite/Postgres).
• Adicione autenticação no painel (login + JWT) e valide grant_request no servidor.
• Use wss:// (TLS) via proxy reverso (Nginx) em produção.
• Proteja endpoints REST com autenticação.
Se quiser, eu adapto o projeto para adicionar autenticação JWT no servidor e no frontend, ou para usar
[Link] em vez de ws . Diga qual opção prefere.