penguin/homekey-relay

homekey-relay

esp32/main/pn532_test.c

raw · 6460 bytes

#include <stdio.h>
#include <string.h>

#include "driver/uart.h"
#include "esp_log.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define PN532_UART	  UART_NUM_1
#define PN532_TX_GPIO   3
#define PN532_RX_GPIO   4
#define PN532_BAUD	  115200

static const char *TAG = "pn532";

static void dump_hex(const char *prefix, const uint8_t *buf, size_t len)
{
	printf("%s", prefix);

	for (size_t i = 0; i < len; i++) {
		printf("%02X ", buf[i]);
	}

	printf("\n");
}

static void pn532_uart_init(void)
{
	const uart_config_t config = {
		.baud_rate = PN532_BAUD,
		.data_bits = UART_DATA_8_BITS,
		.parity = UART_PARITY_DISABLE,
		.stop_bits = UART_STOP_BITS_1,
		.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
		.source_clk = UART_SCLK_DEFAULT,
	};

	ESP_ERROR_CHECK(uart_driver_install(
		PN532_UART,
		1024,
		0,
		0,
		NULL,
		0
	));

	ESP_ERROR_CHECK(uart_param_config(
		PN532_UART,
		&config
	));

	ESP_ERROR_CHECK(uart_set_pin(
		PN532_UART,
		PN532_TX_GPIO,
		PN532_RX_GPIO,
		UART_PIN_NO_CHANGE,
		UART_PIN_NO_CHANGE
	));

	uart_flush_input(PN532_UART);

	ESP_LOGI(
		TAG,
		"UART ready: TX=GPIO%d RX=GPIO%d baud=%d",
		PN532_TX_GPIO,
		PN532_RX_GPIO,
		PN532_BAUD
	);
}

static int pn532_read(uint8_t *buf, size_t size, int timeout_ms)
{
	return uart_read_bytes(
		PN532_UART,
		buf,
		size,
		pdMS_TO_TICKS(timeout_ms)
	);
}

static void pn532_write(const uint8_t *buf, size_t len)
{
	dump_hex("TX: ", buf, len);

	uart_write_bytes(
		PN532_UART,
		buf,
		len
	);

	uart_wait_tx_done(
		PN532_UART,
		pdMS_TO_TICKS(100)
	);
}

static void pn532_wakeup(void)
{
	/*
	 * PN532 HSU wake-up preamble.
	 *
	 * The PN532 manual specifies 55 55 followed by the
	 * normal synchronization sequence for waking from
	 * LowVbat mode.
	 */
	static const uint8_t wakeup[] = {
		0x55, 0x55,
		0x00, 0x00, 0x00, 0x00, 0x00,
		0xFF
	};

	ESP_LOGI(TAG, "sending wakeup");

	pn532_write(wakeup, sizeof(wakeup));

	vTaskDelay(pdMS_TO_TICKS(10));

	uart_flush_input(PN532_UART);
}

static void pn532_get_firmware_version(void)
{
	static const uint8_t cmd[] = {
		/* HSU wakeup */
		0x55, 0x55,
		0x00, 0x00, 0x00, 0x00, 0x00,

		/* PN532 frame */
		0xFF,
		0x02, 0xFE,
		0xD4, 0x02,
		0x2A,
		0x00
	};

	uint8_t buf[64];

	uart_flush_input(PN532_UART);

	ESP_LOGI(TAG, "GetFirmwareVersion");
	pn532_write(cmd, sizeof(cmd));

	int len = pn532_read(buf, sizeof(buf), 1000);

	if (len <= 0) {
		ESP_LOGE(TAG, "no response from PN532");
		return;
	}

	dump_hex("RX: ", buf, len);

	len = pn532_read(buf, sizeof(buf), 500);

	if (len > 0) {
		dump_hex("RX: ", buf, len);
	}
}

static int pn532_read_response(uint8_t *buf, size_t size, int timeout_ms)
{
	int len = pn532_read(buf, size, timeout_ms);

	if (len <= 0) {
		return len;
	}

	dump_hex("RX: ", buf, len);
	return len;
}

static void pn532_poll_card(void)
{
	/*
	 * InListPassiveTarget
	 *
	 * D4 4A 01 00
	 *	│  │  └── BrTy = 0x00: ISO14443A @ 106 kbps
	 *	│  └───── MaxTg = 1
	 *	└──────── command
	 *
	 * Complete PN532 frame:
	 *
	 * 00 00 FF 04 FC D4 4A 01 00 E1 00
	 */
	static const uint8_t cmd[] = {
		0x00, 0x00, 0xFF,
		0x04, 0xFC,
		0xD4, 0x4A, 0x01, 0x00,
		0xE1,
		0x00
	};

	uint8_t buf[128];

	uart_flush_input(PN532_UART);

	pn532_write(cmd, sizeof(cmd));

	int len = pn532_read_response(
		buf,
		sizeof(buf),
		1000
	);

	if (len <= 0) {
		/*
		 * No card is normal.
		 */
		return;
	}

	/*
	 * ACK:
	 *
	 * 00 00 FF 00 FF 00
	 *
	 * Often ACK + response arrive in one UART read.
	 *
	 * Find the response frame:
	 *
	 * 00 00 FF LEN LCS D5 4B ...
	 */
	int frame = -1;

	for (int i = 0; i + 6 < len; i++) {
		if (
			buf[i + 0] == 0x00 &&
			buf[i + 1] == 0x00 &&
			buf[i + 2] == 0xFF &&
			buf[i + 5] == 0xD5 &&
			buf[i + 6] == 0x4B
		) {
			frame = i;
			break;
		}
	}

	/*
	 * Response may have arrived separately from ACK.
	 */
	if (frame < 0) {
		len = pn532_read_response(
			buf,
			sizeof(buf),
			1000
		);

		if (len <= 0) {
			return;
		}

		for (int i = 0; i + 6 < len; i++) {
			if (
				buf[i + 0] == 0x00 &&
				buf[i + 1] == 0x00 &&
				buf[i + 2] == 0xFF &&
				buf[i + 5] == 0xD5 &&
				buf[i + 6] == 0x4B
			) {
				frame = i;
				break;
			}
		}
	}

	if (frame < 0) {
		ESP_LOGW(TAG, "no InListPassiveTarget response found");
		return;
	}

	/*
	 * Payload after D5 4B:
	 *
	 * NbTg
	 *
	 * If NbTg > 0:
	 *
	 * Tg
	 * SENS_RES[2]
	 * SEL_RES
	 * NFCIDLength
	 * NFCID[]
	 */
	int p = frame + 7;

	if (p >= len) {
		return;
	}

	uint8_t targets = buf[p++];

	if (targets == 0) {
		return;
	}

	if (p + 5 > len) {
		ESP_LOGW(TAG, "short target response");
		return;
	}

	uint8_t tg = buf[p++];

	uint8_t sens_res_0 = buf[p++];
	uint8_t sens_res_1 = buf[p++];

	uint8_t sel_res = buf[p++];

	uint8_t uid_len = buf[p++];

	if (uid_len == 0 || uid_len > 10 || p + uid_len > len) {
		ESP_LOGW(TAG, "invalid UID length: %u", uid_len);
		return;
	}

	ESP_LOGI(
		TAG,
		"card: Tg=%u ATQA=%02X%02X SAK=%02X UID len=%u",
		tg,
		sens_res_0,
		sens_res_1,
		sel_res,
		uid_len
	);

	printf("UID: ");

	for (int i = 0; i < uid_len; i++) {
		if (i) {
			printf(":");
		}

		printf("%02X", buf[p + i]);
	}

	printf("\n");
}

static bool pn532_sam_config(void)
{
	/*
	 * HSU wake-up + SAMConfiguration
	 *
	 * D4 14 01 14 01
	 *	   │  │  └── IRQ enabled
	 *	   │  └───── timeout
	 *	   └──────── normal mode
	 *
	 * DCS:
	 * D4 + 14 + 01 + 14 + 01 = FE
	 * checksum = 02
	 */
	static const uint8_t cmd[] = {
		/* HSU wake-up */
		0x55, 0x55,
		0x00, 0x00, 0x00, 0x00, 0x00,

		/* PN532 frame */
		0xFF,
		0x05, 0xFB,
		0xD4, 0x14, 0x01, 0x14, 0x01,
		0x02,
		0x00
	};

	uint8_t buf[64];

	uart_flush_input(PN532_UART);

	ESP_LOGI(TAG, "SAMConfiguration");
	pn532_write(cmd, sizeof(cmd));

	int len = pn532_read(buf, sizeof(buf), 1000);

	if (len <= 0) {
		ESP_LOGE(TAG, "SAMConfiguration: no response");
		return false;
	}

	dump_hex("RX: ", buf, len);

	/*
	 * Expected somewhere in the response:
	 *
	 * ACK:
	 * 00 00 FF 00 FF 00
	 *
	 * response:
	 * 00 00 FF 02 FE D5 15 16 00
	 */

	return true;
}

void app_main(void)
{
	ESP_LOGI(TAG, "PN532 NFC reader");

	pn532_uart_init();

	vTaskDelay(pdMS_TO_TICKS(1000));

	pn532_get_firmware_version();

	vTaskDelay(pdMS_TO_TICKS(100));

	if (!pn532_sam_config()) {
		ESP_LOGE(TAG, "PN532 initialization failed");
		return;
	}

	vTaskDelay(pdMS_TO_TICKS(100));

	while (1) {
		pn532_poll_card();
		vTaskDelay(pdMS_TO_TICKS(300));
	}
}