20  RFID Hands-On Labs and Assessment

Key Concepts
  • RFID Lab Setup: The hardware and software required for RFID experiments: reader, antenna, tags of multiple types/frequencies, and a host system with appropriate software
  • Read Rate Measurement: Counting the number of successful tag reads per second or per antenna sweep, used as a KPI for RFID system performance
  • Tag Inventory: The RFID command that queries all tags in the field simultaneously and returns the EPCs of detected tags; the most common RFID operation
  • Write Verification: Reading back a tag’s memory after writing to confirm the data was stored correctly; essential for data integrity
  • RSSI Mapping: Recording the RSSI (Received Signal Strength Indicator) from each tag at multiple reader positions to create a coverage map
  • Anti-Collision Testing: Measuring read accuracy when multiple tags are presented simultaneously to verify the anti-collision algorithm performance
  • Environmental Impact Testing: Measuring how metal surfaces, liquids, and physical obstructions affect read rate and range

20.1 In 60 Seconds

This chapter provides complete hands-on RFID projects: an ESP32 access control system with LCD, LEDs, and buzzer feedback, plus a Python Flask inventory dashboard with real-time scanning. It also includes comprehensive knowledge check quizzes covering frequency selection, security vulnerabilities, and transit system design.

20.2 Learning Objectives

By the end of this chapter, you will be able to:

  • Construct Access Control Systems: Assemble and program a complete RFID door lock with LCD feedback, LED indicators, buzzer alerts, and circular-buffer logging
  • Implement Inventory Dashboards: Develop a Flask-based web interface that processes real-time RFID scans, stores items in SQLite, and displays live statistics
  • Critique Security Vulnerabilities: Assess why MIFARE Classic Crypto1 is fundamentally broken and justify migration to AES-based alternatives like DESFire EV3
  • Justify Frequency Band Selection: Defend the choice of LF, HF, or UHF for specific scenarios using tissue penetration, range, and standard compliance criteria
  • Evaluate Transit Payment Architectures: Analyze why NFC dominates contactless transit and predict failure modes if UHF were substituted
  • Diagnose RFID Failures: Isolate root causes of poor read rates including metal interference, tag orientation, and RF collision

What is this chapter? Complete hands-on projects and knowledge assessment for RFID systems.

Before starting labs:

  1. Review RFID Hardware Integration
  2. Gather required hardware components
  3. Set up development environment (Arduino IDE or Python)

Hardware for Labs:

Lab Hardware Required Estimated Cost
Lab 1 ESP32 + RC522 + LCD + LEDs $15-20
Lab 2 Raspberry Pi + RC522 $40-50

Assessment Focus:

  • RFID frequency selection
  • Security vulnerabilities
  • Payment system design

20.3 Prerequisites

Before diving into this chapter, you should be familiar with:

20.4 Lab 1: ESP32 RFID Access Control System

Build a complete access control system with LCD display, buzzer feedback, and access logging.

Hardware Required:

  • ESP32 Development Board
  • RC522 RFID Reader Module (13.56 MHz)
  • I2C LCD Display (16x2 or 20x4)
  • Buzzer
  • Green and Red LEDs
  • RFID cards/tags
  • Resistors (220Ω for LEDs, 1kΩ for buzzer)

Wiring:

Component ESP32 Pin
RC522 SDA GPIO 5
RC522 SCK GPIO 18
RC522 MOSI GPIO 23
RC522 MISO GPIO 19
RC522 RST GPIO 4
RC522 3.3V 3.3V
RC522 GND GND
LCD SDA GPIO 21
LCD SCL GPIO 22
Green LED GPIO 25 (via 220Ω resistor)
Red LED GPIO 26 (via 220Ω resistor)
Buzzer GPIO 27 (via 1kΩ resistor)

Complete Code:

#include <SPI.h>
#include <MFRC522.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Pin definitions
#define SS_PIN 5
#define RST_PIN 4
#define GREEN_LED 25
#define RED_LED 26
#define BUZZER 27

// Initialize components
MFRC522 rfid(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);  // I2C address 0x27, 16 cols, 2 rows

// Authorized UIDs (add your card UIDs here)
String authorizedUIDs[] = {
    "04521AB2",        // Employee card 1
    "04A1B2C3D4E5F6",   // Employee card 2
    "D3A512C4"          // Admin card
};

int numAuthorized = sizeof(authorizedUIDs) / sizeof(authorizedUIDs[0]);

// Access log structure
struct AccessLog {
    String uid;
    String timestamp;
    bool granted;
};

AccessLog accessLog[100];  // Store last 100 access attempts
int logIndex = 0;

void setup() {
    Serial.begin(115200);

    // Initialize SPI and RFID
    SPI.begin();
    rfid.PCD_Init();

    // Initialize LCD
    lcd.init();
    lcd.backlight();
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("RFID Access");
    lcd.setCursor(0, 1);
    lcd.print("Control Ready");

    // Initialize LEDs and buzzer
    pinMode(GREEN_LED, OUTPUT);
    pinMode(RED_LED, OUTPUT);
    pinMode(BUZZER, OUTPUT);

    // Test sequence
    digitalWrite(GREEN_LED, HIGH);
    delay(200);
    digitalWrite(GREEN_LED, LOW);
    digitalWrite(RED_LED, HIGH);
    delay(200);
    digitalWrite(RED_LED, LOW);
    tone(BUZZER, 1000, 100);

    Serial.println("\n=== RFID Access Control System ===");
    Serial.println("System initialized. Waiting for cards...\n");

    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Scan Your Card");
}

void loop() {
    // Look for new cards
    if (!rfid.PICC_IsNewCardPresent())
        return;

    // Select one of the cards
    if (!rfid.PICC_ReadCardSerial())
        return;

    // Read card UID
    String uid = "";
    for (byte i = 0; i < rfid.uid.size; i++) {
        uid += String(rfid.uid.uidByte[i] < 0x10 ? "0" : "");
        uid += String(rfid.uid.uidByte[i], HEX);
    }
    uid.toUpperCase();

    // Check authorization
    bool authorized = false;
    for (int i = 0; i < numAuthorized; i++) {
        if (uid.equals(authorizedUIDs[i])) {
            authorized = true;
            break;
        }
    }

    // Display result and log access
    if (authorized) {
        grantAccess(uid);
    } else {
        denyAccess(uid);
    }

    // Halt card and stop encryption
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();

    // Return to ready state after 3 seconds
    delay(3000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Scan Your Card");
    digitalWrite(GREEN_LED, LOW);
    digitalWrite(RED_LED, LOW);
}

void grantAccess(String uid) {
    Serial.println("✓ ACCESS GRANTED");
    Serial.println("  UID: " + uid);
    Serial.println("  Time: " + getTimestamp());

    // LCD display
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("ACCESS GRANTED");
    lcd.setCursor(0, 1);
    lcd.print("Welcome!");

    // Green LED and success beep
    digitalWrite(GREEN_LED, HIGH);
    tone(BUZZER, 2000, 100);
    delay(100);
    tone(BUZZER, 2500, 100);

    // Log access
    logAccess(uid, true);
}

void denyAccess(String uid) {
    Serial.println("✗ ACCESS DENIED");
    Serial.println("  UID: " + uid);
    Serial.println("  Time: " + getTimestamp());
    Serial.println("  Reason: Unauthorized card");

    // LCD display
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("ACCESS DENIED");
    lcd.setCursor(0, 1);
    lcd.print("Unauthorized!");

    // Red LED and error beep
    digitalWrite(RED_LED, HIGH);
    tone(BUZZER, 500, 200);
    delay(250);
    tone(BUZZER, 500, 200);
    delay(250);
    tone(BUZZER, 500, 200);

    // Log access attempt
    logAccess(uid, false);
}

void logAccess(String uid, bool granted) {
    accessLog[logIndex].uid = uid;
    accessLog[logIndex].timestamp = getTimestamp();
    accessLog[logIndex].granted = granted;

    logIndex = (logIndex + 1) % 100;  // Circular buffer
}

String getTimestamp() {
    // In real system, use RTC module for accurate timestamps
    // For now, return milliseconds since boot
    unsigned long ms = millis();
    unsigned long seconds = ms / 1000;
    unsigned long minutes = seconds / 60;
    unsigned long hours = minutes / 60;

    return String(hours % 24) + ":" +
           String(minutes % 60) + ":" +
           String(seconds % 60);
}

void printAccessLog() {
    // Call this function via Serial command or button press
    Serial.println("\n=== ACCESS LOG (Last 10 Entries) ===");

    int start = max(0, logIndex - 10);
    for (int i = start; i < logIndex; i++) {
        Serial.print(accessLog[i].timestamp);
        Serial.print(" | ");
        Serial.print(accessLog[i].uid);
        Serial.print(" | ");
        Serial.println(accessLog[i].granted ? "GRANTED" : "DENIED");
    }
    Serial.println();
}

Objective: Simulate an RFID access control system without physical RFID hardware using ESP32’s serial input.

Paste this code into the Wokwi editor:

#include <Arduino.h>

// Simulated authorized UIDs
const char* authorizedUIDs[] = {
  "04521AB2", "D3A512C4", "A1B2C3D4"
};
const int NUM_AUTHORIZED = 3;

// Access log
struct AccessEntry {
  String uid;
  unsigned long timestamp;
  bool granted;
};

AccessEntry accessLog[50];
int logIndex = 0;
int totalScans = 0;
int grantedCount = 0;
int deniedCount = 0;

// Simulated tag UIDs (cycles through these)
const char* simulatedTags[] = {
  "04521AB2", "F7A9B2C1", "D3A512C4",
  "DEADBEEF", "A1B2C3D4", "CAFEBABE"
};
const int NUM_TAGS = 6;

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("========================================");
  Serial.println("  RFID Access Control System Simulator");
  Serial.println("========================================\n");
  Serial.println("System initialized. Scanning for tags...\n");
  Serial.println("[Simulates MFRC522 RC522 reader on SPI]");
  Serial.println("[Green LED = GPIO 25, Red LED = GPIO 26]\n");
}

void loop() {
  // Simulate random tag detection every 3-5 seconds
  int tagIndex = random(0, NUM_TAGS);
  const char* uid = simulatedTags[tagIndex];
  totalScans++;

  Serial.println("--- Tag Detected ---");
  Serial.printf("  UID: %s\n", uid);
  Serial.printf("  PICC Type: MIFARE 1KB\n");

  // Check authorization
  bool authorized = false;
  for (int i = 0; i < NUM_AUTHORIZED; i++) {
    if (strcmp(uid, authorizedUIDs[i]) == 0) {
      authorized = true;
      break;
    }
  }

  if (authorized) {
    grantedCount++;
    Serial.println("  Result: ACCESS GRANTED");
    Serial.println("  [LCD] Welcome!");
    Serial.println("  [GPIO 25] GREEN LED ON");
    Serial.println("  [BUZZER] Success tone (2000Hz, 100ms)");
  } else {
    deniedCount++;
    Serial.println("  Result: ACCESS DENIED");
    Serial.println("  [LCD] Unauthorized!");
    Serial.println("  [GPIO 26] RED LED ON");
    Serial.println("  [BUZZER] Error tone (500Hz, 200ms x3)");
  }

  // Log access
  if (logIndex < 50) {
    accessLog[logIndex].uid = uid;
    accessLog[logIndex].timestamp = millis();
    accessLog[logIndex].granted = authorized;
    logIndex++;
  }

  // Print statistics every 5 scans
  if (totalScans % 5 == 0) {
    Serial.println("\n=== Access Statistics ===");
    Serial.printf("  Total scans:  %d\n", totalScans);
    Serial.printf("  Granted:      %d (%.0f%%)\n",
      grantedCount, 100.0 * grantedCount / totalScans);
    Serial.printf("  Denied:       %d (%.0f%%)\n",
      deniedCount, 100.0 * deniedCount / totalScans);
    Serial.println("  Last 5 entries:");
    int start = max(0, logIndex - 5);
    for (int i = start; i < logIndex; i++) {
      Serial.printf("    %8lu ms | %s | %s\n",
        accessLog[i].timestamp, accessLog[i].uid.c_str(),
        accessLog[i].granted ? "GRANTED" : "DENIED");
    }
    Serial.println("========================\n");
  }

  Serial.println();
  delay(3000 + random(0, 2000));
}

What to Observe:

  1. The simulator cycles through authorized and unauthorized UIDs showing the access control logic
  2. Access statistics update every 5 scans showing grant/deny percentages
  3. The access log tracks timestamps and UIDs in a circular buffer
  4. GPIO pin states and buzzer tones are logged to show what physical hardware would do

Expected Output (Serial Monitor):

=== RFID Access Control System ===
System initialized. Waiting for cards...

✓ ACCESS GRANTED
  UID: 04521AB2
  Time: 0:5:12
✗ ACCESS DENIED
  UID: F7A9B2C1
  Time: 0:5:45
  Reason: Unauthorized card
✓ ACCESS GRANTED
  UID: E2801160600002A1
  Time: 0:6:03

Expected Output (LCD Display):

[When ready]
Scan Your Card

[On authorized card]
ACCESS GRANTED
Welcome!

[On unauthorized card]
ACCESS DENIED
Unauthorized!

20.5 Lab 2: Python RFID Inventory Dashboard with Real-Time Monitoring

Build a complete inventory management dashboard with web interface using Flask.

Requirements:

pip install Flask mfrc522 RPi.GPIO sqlite3

Web Dashboard Template (templates/dashboard.html):

<!DOCTYPE html>
<html>
<head>
    <title>RFID Inventory Dashboard</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial, sans-serif; background: #f5f5f5; padding: 20px; }
        .container { max-width: 1400px; margin: 0 auto; }
        h1 { color: #333; margin-bottom: 20px; }

        .stats {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }

        .stat-card {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .stat-card h3 { color: #666; font-size: 14px; margin-bottom: 10px; }
        .stat-card .value { font-size: 32px; font-weight: bold; color: #4CAF50; }

        .sections {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
        }

        .section {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        .section h2 { margin-bottom: 15px; color: #333; }

        table { width: 100%; border-collapse: collapse; }
        th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
        th { background: #f9f9f9; font-weight: 600; color: #666; }

        .live-scan {
            background: #E3F2FD;
            padding: 15px;
            border-radius: 8px;
            border-left: 4px solid #2196F3;
            margin-bottom: 20px;
        }

        .live-scan.active {
            background: #C8E6C9;
            border-left-color: #4CAF50;
        }

        .timestamp { color: #999; font-size: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>RFID Inventory Dashboard</h1>

        <div class="live-scan" id="liveScan">
            <strong>Last Scan:</strong> <span id="lastScanInfo">Waiting for tags...</span>
        </div>

        <div class="stats">
            <div class="stat-card">
                <h3>Total Items</h3>
                <div class="value" id="totalItems">0</div>
            </div>
            <div class="stat-card">
                <h3>Scans Today</h3>
                <div class="value" id="scansToday">0</div>
            </div>
            <div class="stat-card">
                <h3>Categories</h3>
                <div class="value" id="categories">0</div>
            </div>
        </div>

        <div class="sections">
            <div class="section">
                <h2>Inventory Items</h2>
                <table>
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Category</th>
                            <th>Last Seen</th>
                        </tr>
                    </thead>
                    <tbody id="itemsTable">
                        <tr><td colspan="3">Loading...</td></tr>
                    </tbody>
                </table>
            </div>

            <div class="section">
                <h2>Recent Activity</h2>
                <table>
                    <thead>
                        <tr>
                            <th>Item</th>
                            <th>Location</th>
                            <th>Time</th>
                        </tr>
                    </thead>
                    <tbody id="activityTable">
                        <tr><td colspan="3">Loading...</td></tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    <script>
        // Update statistics
        function updateStats() {
            fetch('/api/statistics')
                .then(r => r.json())
                .then(data => {
                    document.getElementById('totalItems').textContent = data.total_items;
                    document.getElementById('scansToday').textContent = data.scans_today;
                    document.getElementById('categories').textContent =
                        Object.keys(data.by_category).length;
                });
        }

        // Update items table
        function updateItems() {
            fetch('/api/items')
                .then(r => r.json())
                .then(items => {
                    const tbody = document.getElementById('itemsTable');
                    tbody.innerHTML = items.map(item => `
                        <tr>
                            <td><strong>${item.name}</strong><br>
                                <span class="timestamp">${item.rfid_uid}</span></td>
                            <td>${item.category}</td>
                            <td class="timestamp">${item.last_seen || 'Never'}</td>
                        </tr>
                    `).join('');
                });
        }

        // Update recent scans
        function updateActivity() {
            fetch('/api/recent_scans')
                .then(r => r.json())
                .then(scans => {
                    const tbody = document.getElementById('activityTable');
                    tbody.innerHTML = scans.slice(0, 10).map(scan => `
                        <tr>
                            <td>${scan.item_name}</td>
                            <td>${scan.location}</td>
                            <td class="timestamp">${new Date(scan.timestamp).toLocaleTimeString()}</td>
                        </tr>
                    `).join('');
                });
        }

        // Check for new scans (live updates)
        let lastScanUID = null;
        function checkLastScan() {
            fetch('/api/last_scan')
                .then(r => r.json())
                .then(data => {
                    if (data.uid && data.uid !== lastScanUID) {
                        lastScanUID = data.uid;
                        const liveScan = document.getElementById('liveScan');
                        const scanInfo = document.getElementById('lastScanInfo');

                        liveScan.classList.add('active');
                        scanInfo.textContent = `${data.name} (${data.uid}) - ${new Date(data.timestamp).toLocaleTimeString()}`;

                        // Remove highlight after 3 seconds
                        setTimeout(() => liveScan.classList.remove('active'), 3000);

                        // Refresh data
                        updateStats();
                        updateItems();
                        updateActivity();
                    }
                });
        }

        // Initial load
        updateStats();
        updateItems();
        updateActivity();

        // Periodic updates
        setInterval(checkLastScan, 1000);  // Check for new scans every second
        setInterval(updateStats, 5000);     // Update stats every 5 seconds
        setInterval(updateItems, 10000);    // Update items every 10 seconds
        setInterval(updateActivity, 5000);  // Update activity every 5 seconds
    </script>
</body>
</html>

Running the Dashboard:

python3 rfid_dashboard.py

Expected Output:

Starting RFID Inventory Dashboard...
RFID scanner thread started...
Access dashboard at: http://localhost:5000
 * Running on http://0.0.0.0:5000

Tag detected: 1234567890
  Item: Arduino Uno R3
Tag detected: 9876543210
  Item: Raspberry Pi 4

20.6 Knowledge Check

Test your understanding of RFID technology and applications.

The Sensor Squad decided to build their own RFID door lock! Max the Microcontroller was the brain of the operation. “I’ll check every card that comes near the reader,” he said.

Sammy the Sensor (playing the RC522 reader) stood by the door: “I’ll sense the radio waves from each card and tell Max the secret number.”

Lila the LED had two jobs: “I’ll glow GREEN when a friend’s card is scanned, and RED when a stranger tries to get in!” She practiced flipping between colors.

Bella the Battery made sure everyone had power: “Without me, none of this works! I keep the ESP32, the reader, the screen, and the LEDs all running.”

When their teacher’s card was scanned – BEEP! Green light! “ACCESS GRANTED” appeared on the tiny screen. When a random card was tried – BUZZ BUZZ BUZZ! Red light! “ACCESS DENIED!”

The lesson: You can build real security systems with just a few parts: a microcontroller, an RFID reader, some LEDs, and a bit of code. Start simple, then add features like logging and displays!

Why LF is correct for pet microchips:

  1. Tissue Penetration Physics: LF wavelength (~2,200m) >> tissue thickness, resulting in minimal absorption (0.01-0.05 dB/cm). UHF at 915 MHz has high absorption (0.5-2.0 dB/cm) because water content absorbs the signal.

  2. ISO 11784/11785 Compliance: The global standard for animal identification specifies 134.2 kHz (LF band). Non-compliant chips cannot be read by standard veterinary scanners.

  3. Passive Tag Operation: LF chips harvest energy from the reader’s magnetic field via inductive coupling, lasting 25+ years without batteries.

Why other options are wrong:

  • A (HF): Not ISO 11784 compliant, poorer tissue penetration
  • B (UHF): Water in tissue absorbs 80-90% of signal, unreliable through tissue
  • D (Microwave): This is literally microwave oven frequency - completely absorbed by tissue

When choosing an RFID reader platform for a production deployment, three factors dominate: scale (how many tags per second), environment (networked vs standalone), and budget (cost per reader). This framework helps you avoid overbuilding (wasting money on Arduino for simple tasks) or underbuilding (using ESP32 when you need industrial reliability).

Decision Matrix:

Requirement Arduino Uno ESP32 Raspberry Pi Commercial Reader
Tags/sec 5-10 20-50 100+ 500+
Network Serial only Wi-Fi/BLE Ethernet/Wi-Fi Ethernet/PoE
Power 5V USB/barrel 3.3V micro-USB 5V 3A USB-C 12-48V PoE
Storage 32 KB flash 4 MB flash 32 GB SD Enterprise DB
Cost $25 $10 $45 $200-2,000
Reliability Hobby Prototype Production-lite Industrial
Best For Learning labs Wi-Fi prototypes Edge gateways 24/7 operations

Step-by-Step Selection Process:

1. Define Your Throughput:

  • Under 10 tags/sec (e.g., door access with 1 scan every 10 seconds) → Arduino or ESP32
  • 10-100 tags/sec (e.g., inventory spot-checks with handheld scanner) → Raspberry Pi
  • Over 100 tags/sec (e.g., conveyor belt with bulk scanning) → Commercial UHF reader

2. Network Requirements:

  • No network (standalone logger) → Arduino with SD card shield
  • Wi-Fi cloud sync (periodic uploads) → ESP32 with MQTT
  • Always-connected (real-time dashboard) → Raspberry Pi with Ethernet or commercial reader with PoE
  • Multi-reader coordination (zone tracking) → Commercial readers with central RFID middleware

3. Power and Mounting:

  • Battery-powered (mobile/handheld) → ESP32 with 18650 LiPo (runs 8+ hours)
  • USB-powered (desk/bench) → Arduino or Raspberry Pi
  • PoE installation (ceiling-mounted fixed readers) → Commercial reader with IEEE 802.3af PoE

4. Software Ecosystem:

  • Custom firmware (C/C++ embedded) → Arduino or ESP32 (FreeRTOS)
  • Python scripting (rapid prototyping) → Raspberry Pi
  • Enterprise integration (EPCIS, SAP, WMS) → Commercial reader with LLRP protocol

5. Budget Constraints:

  • Under $50 per reader: Arduino ($25 + RC522 $5) or ESP32 ($10 + RC522 $5)
  • $50-$100 per reader: Raspberry Pi Zero W ($15) or Pi 4 ($45) + MFRC522 HAT
  • Over $100 per reader: Evaluate commercial readers (Zebra FX series, Impinj R700, Nordic ID Sampo)

Real-World Example:

A library implements self-checkout kiosks: - Throughput: 5 books per checkout, 1 checkout every 30 seconds = ~0.2 tags/sec - Network: Wi-Fi for cloud inventory sync - Power: USB-powered from kiosk PC - Budget: $50 per station for 20 stations = $1,000 total

Selection: ESP32 + RC522 ($15 per station). Arduino Uno works but ESP32’s built-in Wi-Fi eliminates the need for separate Ethernet shields ($20 each). Raspberry Pi is overkill – the library does not need 100 tags/sec throughput or Python flexibility.

Total cost: 20 × $15 = $300. Saved $700 versus Pi-based solution, $39,000 versus commercial readers.

The Lesson: Match the reader platform to actual requirements. Commercial readers provide enterprise-grade reliability (99.9% uptime, industrial temperature range, vendor support) but cost 10-100× more than DIY solutions. For prototypes and low-scale production (under 50 readers), ESP32 or Raspberry Pi is often the sweet spot. For mission-critical 24/7 operations, pay for commercial reliability.

Deep Dives:

Comparisons:

Learning:

Common Pitfalls

Tags with incorrect or factory-default EPCs produce misleading inventory results in lab exercises. Fix: programme fresh tags with known EPCs at the start of every lab session and verify the EPC before using tags in experiments.

Placing 5 tags vs 50 tags in the field produces dramatically different read rates but is often not held constant between experimental conditions. Fix: control the number of tags in the field as an independent variable and measure read rate separately for 5, 10, 20, and 50 tags.

A tag oriented perpendicular to the polarisation of the reader antenna reads very weakly. RSSI measurements that assume the worst case may be overly pessimistic; those that assume best-case orientation are too optimistic. Fix: measure RSSI at multiple tag orientations (0°, 45°, 90°) and report the minimum (worst-case) for deployment planning.

20.7 Summary

This chapter provided hands-on labs and assessment for RFID systems:

  • Lab 1: ESP32 Access Control: Complete door lock system with RC522, LCD display, LED indicators, buzzer feedback, and access logging using circular buffer
  • Lab 2: Python Dashboard: Flask-based web interface with real-time RFID scanning, inventory tracking, and periodic AJAX updates
  • Security Assessment: MIFARE Classic Crypto1 vulnerability (broken 2008) vs DESFire EV3 AES-128 security
  • Frequency Selection: LF for tissue penetration (pet microchips), HF/NFC for payments (<10cm range), UHF for inventory (1-12m range)
  • Transit Systems: NFC is universal for payments (Apple Pay, Google Pay, EMV) - UHF’s long range causes accidental charges

20.8 Concept Relationships

Builds On:

Enables:

  • RFID Security and Privacy - Upgrading from UID to challenge-response authentication
  • Production RFID deployments with proper security and logging

Related Concepts:

  • ESP32 circular buffer logging scales to SD card storage for production systems
  • Flask dashboards provide real-time visibility into RFID inventory events
  • LF 134.2 kHz is mandatory for pet microchips due to tissue penetration physics

20.9 See Also

Lab Extensions:

Hardware Upgrades:

Security Enhancements:

20.10 Try It Yourself

Lab 1 Enhancement: Add an SD card module to the ESP32 access control system. Log each access attempt as a CSV line: timestamp, UID, result (granted/denied), location. Implement a web server (ESP32WebServer) that serves the log file via HTTP for download and analysis.

Lab 2 Enhancement: Extend the Flask inventory dashboard with category-based filtering. Add a search bar that filters items by name or UID. Implement export functionality: “Download as CSV” button that generates a timestamped inventory report. Add bar charts showing items per category.

Cross-Lab Integration: Combine Lab 1 and Lab 2. ESP32 access control publishes to MQTT (same pattern as Python smart home). The Flask dashboard subscribes to the access MQTT topic and displays real-time access events alongside inventory scans. Create a unified dashboard showing both systems.

Production Deployment: Build a complete RFID asset tracking system for 20 items (tools, equipment, books). Tag each item with NTAG213. Create a Python Flask app with: (1) item registration (scan tag, enter name/category), (2) check-out/check-in logging, (3) “missing items” report (items not scanned in 7 days), (4) utilization statistics (most/least frequently used items).

20.11 What’s Next

Chapter Focus Link
RFID Security and Privacy Cryptographic authentication, privacy protocols, secure deployment rfid-security-and-privacy.html
RFID Design and Deployment Production system design, RF site surveys, tag selection rfid-design-and-deployment.html
NFC Programming and Tags NDEF records, tag writing, smartphone NFC integration nfc-apps-programming-tags.html
RFID Troubleshooting Diagnosing read failures, metal interference, and antenna tuning rfid-troubleshooting.html