Komponenty a zapojenie RC522
RC522 je populárna RFID čítačka pracujúca na 13,56 MHz (ISO 14443A). Komunikuje cez SPI rozhranie. Kompatibilná s bežnými MIFARE kartami a kľúčenkami.
| RC522 pin | ESP32 pin |
|---|---|
| SDA (SS) | GPIO5 |
| SCK | GPIO18 |
| MOSI | GPIO23 |
| MISO | GPIO19 |
| RST | GPIO22 |
| GND | GND |
| 3.3V | 3.3V |
ESP32 kód — čítanie UID karty
Knižnica MFRC522 od miguelbalboa je štandard pre Arduino/ESP32. Inštaluj cez Library Manager.
#include <SPI.h>
#include <MFRC522.h>
#include <WiFi.h>
#include <HTTPClient.h>
#define SS_PIN 5
#define RST_PIN 22
MFRC522 rfid(SS_PIN, RST_PIN);
const char* API_URL = "https://api.gear.sk/api/attendance";
const char* API_KEY = "tajny-kluc";
const char* READER_ID = "vstup-hlavny";
String getUID(MFRC522::Uid uid) {
String result = "";
for (byte i = 0; i < uid.size; i++) {
result += (uid.uidByte[i] < 0x10 ? "0" : "");
result += String(uid.uidByte[i], HEX);
}
result.toUpperCase();
return result;
}
void sendScan(String uid) {
HTTPClient http;
http.begin(API_URL);
http.addHeader("Content-Type", "application/json");
http.addHeader("X-API-Key", API_KEY);
String body = "{\"uid\":\"" + uid + "\",\"reader\":\"" + READER_ID + "\"}";
int code = http.POST(body);
if (code == 200) {
// Zelené LED / zvukový signál = OK
Serial.println("Scan OK: " + uid);
} else {
// Červené LED = neznáma karta alebo chyba
Serial.println("Scan FAIL: " + String(code));
}
http.end();
}
void setup() {
SPI.begin();
rfid.PCD_Init();
WiFi.begin("SSID", "PASS");
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("Pripraveny");
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) {
delay(50);
return;
}
String uid = getUID(rfid.uid);
sendScan(uid);
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
delay(2000); // cooldown — zabraňuje duplicitným scanom
}
Laravel backend — migrácie a modely
// database/migrations/create_rfid_cards_table.php
Schema::create('rfid_cards', function (Blueprint $table) {
$table->id();
$table->string('uid', 20)->unique();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->boolean('active')->default(true);
$table->timestamps();
});
// database/migrations/create_attendance_logs_table.php
Schema::create('attendance_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('rfid_card_id')->constrained();
$table->foreignId('user_id')->constrained();
$table->string('reader_id');
$table->enum('type', ['in', 'out']);
$table->timestamp('scanned_at')->useCurrent();
$table->index(['user_id', 'scanned_at']);
});
Controller — príjem scanu
// app/Http/Controllers/AttendanceController.php
class AttendanceController extends Controller
{
public function scan(Request $request): JsonResponse
{
$request->validate([
'uid' => 'required|string|max:20',
'reader' => 'required|string',
]);
$card = RfidCard::with('user')
->where('uid', strtoupper($request->uid))
->where('active', true)
->first();
if (!$card) {
return response()->json(['status' => 'unknown'], 403);
}
// Zisti typ: ak posledný záznam je 'in', teraz je 'out' a naopak
$last = AttendanceLog::where('user_id', $card->user_id)
->latest('scanned_at')
->first();
$type = (!$last || $last->type === 'out') ? 'in' : 'out';
AttendanceLog::create([
'rfid_card_id' => $card->id,
'user_id' => $card->user_id,
'reader_id' => $request->reader,
'type' => $type,
]);
return response()->json([
'status' => 'ok',
'name' => $card->user->name,
'type' => $type,
]);
}
}
Laravel routy
// routes/api.php
use App\Http\Controllers\AttendanceController;
use App\Http\Controllers\AttendanceExportController;
Route::middleware('api.key')->group(function () {
Route::post('/attendance', [AttendanceController::class, 'scan']);
});
// Web dashboard a export — chránený Sanctum autentifikáciou
Route::middleware('auth:sanctum')->group(function () {
Route::get('/attendance/export', [AttendanceExportController::class, 'export']);
});
Middleware — overenie X-API-Key
// app/Http/Middleware/ApiKeyMiddleware.php
class ApiKeyMiddleware
{
public function handle(Request $request, Closure $next): Response
{
if ($request->header('X-API-Key') !== config('iot.rfid_key')) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $next($request);
}
}
// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias(['api.key' => ApiKeyMiddleware::class]);
})
CSV export pre mzdové účtovníctvo
// app/Http/Controllers/AttendanceExportController.php
public function export(Request $request): StreamedResponse
{
$from = $request->date('from', 'Y-m-d', now()->startOfMonth());
$to = $request->date('to', 'Y-m-d', now()->endOfMonth());
$logs = AttendanceLog::with('user')
->whereBetween('scanned_at', [$from, $to])
->orderBy('user_id')
->orderBy('scanned_at')
->get();
return response()->streamDownload(function () use ($logs) {
$handle = fopen('php://output', 'w');
fputcsv($handle, ['Meno', 'Typ', 'Čítačka', 'Čas']);
foreach ($logs as $log) {
fputcsv($handle, [
$log->user->name,
$log->type === 'in' ? 'Príchod' : 'Odchod',
$log->reader_id,
$log->scanned_at->format('d.m.Y H:i:s'),
]);
}
fclose($handle);
}, 'dochadzka-' . $from->format('Y-m') . '.csv', [
'Content-Type' => 'text/csv; charset=UTF-8',
]);
}
Registrácia novej karty
// Admin rozhranie — registrácia karty k zamestnancovi
// POST /admin/rfid-cards
public function register(Request $request): RedirectResponse
{
$request->validate([
'uid' => 'required|string|unique:rfid_cards,uid',
'user_id' => 'required|exists:users,id',
]);
RfidCard::create([
'uid' => strtoupper($request->uid),
'user_id' => $request->user_id,
'active' => true,
]);
return redirect()->back()->with('success', 'Karta zaregistrovaná.');
}
Rozšírenia systému
- Pridaj OLED displej k ESP32 — zobrazuje meno a čas po priložení karty
- Bzučiak (piezo buzzer) — zvukový signál OK/FAIL
- RGB LED — zelená = príchod, červená = odchod, modrá = neznáma karta
- Zálohovanie scanov do RTC pamäte pri výpadku WiFi — odošli pri obnovení spojenia
- GDPR: záznamy o pohybe zamestnancov sú osobné údaje — informuj ich a nastav retenčnú dobu (napr. 3 roky)