Implementación de comunicación NFC con el módulo PN532 en Raspberry Pi

El módulo PN532 es uno de los chips NFC más versátiles del mercado, permitiendo la comunicación a través de interfaces UART, I2C y SPI. En entornos de desarrollo con Raspberry Pi, su integración mediante un HAT facilita la lectura de tarjetas Mifare Classic y otros dispositivos compatibles.

Comunicación básica mediante interfaz UART

Para establecer una conexión directa por puerto serie, es fundamental identificar el dispositivo correcto en el sistema. El siguiente script automatiza la búsqueda del puerto activo y extrae el identificador único (UID) de la tarjeta detectada.

import serial
import time
import os

# Configuración de parámetros de comunicación
PUERTOS_SERIALES = ['/dev/ttyS0', '/dev/ttyAMA0', '/dev/ttyUSB0']
VELOCIDAD_BAUDIOS = 115200

def inicializar_conexion_uart():
    for ruta in PUERTOS_SERIALES:
        if os.path.exists(ruta):
            try:
                conexion = serial.Serial(ruta, VELOCIDAD_BAUDIOS, timeout=1)
                if conexion.is_open:
                    print(f"Enlace establecido en: {ruta}")
                    return conexion
            except Exception as error:
                print(f"Error al abrir {ruta}: {error}")
    return None

# Comandos en formato hexadecimal para el PN532
BUFFER_DESPERTAR = bytearray([0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD4, 0x14, 0x01, 0x17, 0x00])
BUFFER_BUSQUEDA = bytearray([0x00, 0x00, 0xFF, 0x04, 0xFC, 0xD4, 0x4A, 0x01, 0x00, 0xE1, 0x00])

def ejecutar_comando(serial_obj, comando):
    serial_obj.write(comando)
    time.sleep(0.15)
    return serial_obj.read(serial_obj.in_waiting)

def procesar_lectura_nfc(serial_obj):
    # Intentar despertar el chip
    ejecutar_comando(serial_obj, BUFFER_DESPERTAR)
    # Solicitar detección de tarjeta
    trama_respuesta = ejecutar_comando(serial_obj, BUFFER_BUSQUEDA)
    
    if trama_respuesta and len(trama_respuesta) > 12:
        # El UID suele encontrarse a partir del byte 8 en la trama de respuesta
        id_tarjeta = trama_respuesta[8:14]
        return id_tarjeta
    return None

if __name__ == "__main__":
    nfc_serial = inicializar_conexion_uart()
    if nfc_serial:
        try:
            while True:
                uid_detectado = procesar_lectura_nfc(nfc_serial)
                if uid_detectado:
                    print(f"Tarjeta detectada. UID: {[hex(b) for b in uid_detectado]}")
                else:
                    print("Buscando tarjeta...")
                time.sleep(2)
        except KeyboardInterrupt:
            nfc_serial.close()

Arquitectura Multi-interfaz con Flask

Para aplicaciones más complejas, es útil disponer de un backend que gestione dinámicamente el protocolo de comunicación (UART, I2C o SPI) y exponga los datos mediante una API web.

from flask import Flask, render_template, jsonify, request
import time
import serial
import smbus
import spidev
import os

app = Flask(__name__)

# Configuración de hardware
BUS_I2C = 1
ADDR_PN532 = 0x24
SPI_CHANNEL = 0
SPI_DEVICE = 0

class GestorNFC:
    def __init__(self):
        self.driver = None
        self.tipo = None

    def conectar(self, modo):
        self.tipo = modo
        try:
            if modo == 'uart':
                self.driver = serial.Serial('/dev/ttyS0', 115200, timeout=0.5)
            elif modo == 'i2c':
                self.driver = smbus.SMBus(BUS_I2C)
            elif modo == 'spi':
                self.driver = spidev.SpiDev()
                self.driver.open(SPI_CHANNEL, SPI_DEVICE)
                self.driver.max_speed_hz = 1000000
            return True
        except Exception as e:
            print(f"Fallo en conexión {modo}: {e}")
            return False

    def leer_uid(self):
        cmd_scan = bytearray([0x00, 0x00, 0xFF, 0x04, 0xFC, 0xD4, 0x4A, 0x01, 0x00, 0xE1, 0x00])
        if self.tipo == 'uart':
            self.driver.write(cmd_scan)
            time.sleep(0.1)
            res = self.driver.read(20)
        # Lógica simplificada para demostración de tramas
        if res and len(res) > 10:
            return [hex(x) for x in res[8:12]]
        return None

nfc_manager = GestorNFC()

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/api/scan', methods=['GET'])
def api_scan():
    protocolo = request.args.get('proto', 'uart')
    if nfc_manager.conectar(protocolo):
        uid = nfc_manager.leer_uid()
        if uid:
            return jsonify({"status": "success", "uid": uid})
        return jsonify({"status": "no_card"}), 404
    return jsonify({"status": "error", "message": "Hardware no disponible"}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

Interfaz de Usuario (index.html)

A continuación se presenta una estructura HTML minimalista para interactuar con el servidor Flask y visualizar los resultados del escaneo en tiempo real.


<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Panel de Control NFC</title>
    <style>
        .contenedor { max-width: 500px; margin: 40px auto; text-align: center; font-family: sans-serif; }
        .resultado { margin-top: 20px; padding: 15px; border: 1px solid #ccc; background: #f9f9f9; }
    </style>
</head>
<body>
    <div class="contenedor">
        <h1>Escáner PN532</h1>
        <select id="protocolo">
            <option value="uart">UART</option>
            <option value="i2c">I2C</option>
            <option value="spi">SPI</option>
        </select>
        <button onclick="escanearTarjeta()">Leer UID</button>
        
        <div class="resultado" id="visor-uid">Esperando lectura...</div>
    </div>

    <script>
        function escanearTarjeta() {
            const proto = document.getElementById('protocolo').value;
            const visor = document.getElementById('visor-uid');
            visor.innerText = "Escaneando...";

            fetch(`/api/scan?proto=${proto}`)
                .then(res => res.json())
                .then(data => {
                    if (data.status === "success") {
                        visor.innerText = "UID Detectado: " + data.uid.join(' ');
                    } else {
                        visor.innerText = "Error: " + (data.message || "No se detectó tarjeta");
                    }
                })
                .catch(err => visor.innerText = "Fallo en el servidor");
        }
    </script>
</body>
</html>

Etiquetas: Raspberry Pi NFC PN532 Python Flask

Publicado el 7-12 05:54