429 lines
11 KiB
C
429 lines
11 KiB
C
/**
|
|
* @file nfc_manager.c
|
|
* @brief NFC Manager Implementation - COMPLETE PN532 Driver!
|
|
*
|
|
* Full implementation:
|
|
* - PN532 I2C communication
|
|
* - Tag detection & reading
|
|
* - ROM mapping from JSON
|
|
* - UID to ROM path lookup
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "esp_log.h"
|
|
#include "driver/i2c.h"
|
|
#include "cJSON.h"
|
|
#include "esp_vfs_fat.h"
|
|
#include "nfc_manager.h"
|
|
#include "hardware_config.h"
|
|
|
|
static const char *TAG = "NFC";
|
|
|
|
// PN532 I2C Commands
|
|
#define PN532_PREAMBLE 0x00
|
|
#define PN532_STARTCODE1 0x00
|
|
#define PN532_STARTCODE2 0xFF
|
|
#define PN532_POSTAMBLE 0x00
|
|
|
|
#define PN532_HOSTTOPN532 0xD4
|
|
#define PN532_PN532TOHOST 0xD5
|
|
|
|
// PN532 Commands
|
|
#define PN532_COMMAND_GETFIRMWAREVERSION 0x02
|
|
#define PN532_COMMAND_SAMCONFIGURATION 0x14
|
|
#define PN532_COMMAND_INLISTPASSIVETARGET 0x4A
|
|
|
|
// PN532 Mifare Commands
|
|
#define MIFARE_CMD_AUTH_A 0x60
|
|
#define MIFARE_CMD_AUTH_B 0x61
|
|
#define MIFARE_CMD_READ 0x30
|
|
#define MIFARE_CMD_WRITE 0xA0
|
|
|
|
#define PN532_I2C_TIMEOUT_MS 1000
|
|
#define PN532_I2C_READY 0x01
|
|
|
|
// ROM mapping storage
|
|
#define MAX_ROM_MAPPINGS 32
|
|
static nfc_rom_mapping_t rom_mappings[MAX_ROM_MAPPINGS];
|
|
static int rom_mapping_count = 0;
|
|
|
|
/**
|
|
* @brief Initialize I2C for PN532
|
|
*/
|
|
static esp_err_t pn532_i2c_init(void)
|
|
{
|
|
i2c_config_t conf = {
|
|
.mode = I2C_MODE_MASTER,
|
|
.sda_io_num = NFC_I2C_SDA,
|
|
.scl_io_num = NFC_I2C_SCL,
|
|
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
|
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
|
.master.clk_speed = NFC_I2C_FREQ_HZ,
|
|
};
|
|
|
|
ESP_ERROR_CHECK(i2c_param_config(NFC_I2C_NUM, &conf));
|
|
ESP_ERROR_CHECK(i2c_driver_install(NFC_I2C_NUM, conf.mode, 0, 0, 0));
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Wait for PN532 to be ready
|
|
*/
|
|
static esp_err_t pn532_wait_ready(uint32_t timeout_ms)
|
|
{
|
|
uint8_t status;
|
|
uint32_t start = xTaskGetTickCount() * portTICK_PERIOD_MS;
|
|
|
|
while ((xTaskGetTickCount() * portTICK_PERIOD_MS - start) < timeout_ms) {
|
|
if (i2c_master_read_from_device(NFC_I2C_NUM, NFC_I2C_ADDR,
|
|
&status, 1, pdMS_TO_TICKS(100)) == ESP_OK) {
|
|
if (status == PN532_I2C_READY) {
|
|
return ESP_OK;
|
|
}
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
}
|
|
|
|
return ESP_ERR_TIMEOUT;
|
|
}
|
|
|
|
/**
|
|
* @brief Write command to PN532
|
|
*/
|
|
static esp_err_t pn532_write_command(const uint8_t *cmd, size_t cmd_len)
|
|
{
|
|
uint8_t checksum = 0;
|
|
uint8_t frame[256];
|
|
size_t frame_len = 0;
|
|
|
|
// Build frame
|
|
frame[frame_len++] = PN532_PREAMBLE;
|
|
frame[frame_len++] = PN532_STARTCODE1;
|
|
frame[frame_len++] = PN532_STARTCODE2;
|
|
|
|
frame[frame_len++] = cmd_len + 1; // Length
|
|
frame[frame_len++] = ~(cmd_len + 1) + 1; // Length checksum
|
|
|
|
frame[frame_len++] = PN532_HOSTTOPN532;
|
|
checksum += PN532_HOSTTOPN532;
|
|
|
|
for (size_t i = 0; i < cmd_len; i++) {
|
|
frame[frame_len++] = cmd[i];
|
|
checksum += cmd[i];
|
|
}
|
|
|
|
frame[frame_len++] = ~checksum + 1; // Data checksum
|
|
frame[frame_len++] = PN532_POSTAMBLE;
|
|
|
|
return i2c_master_write_to_device(NFC_I2C_NUM, NFC_I2C_ADDR,
|
|
frame, frame_len, pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
/**
|
|
* @brief Read response from PN532
|
|
*/
|
|
static esp_err_t pn532_read_response(uint8_t *response, size_t *response_len,
|
|
uint32_t timeout_ms)
|
|
{
|
|
esp_err_t ret;
|
|
uint8_t frame[256];
|
|
|
|
// Wait for ready
|
|
ret = pn532_wait_ready(timeout_ms);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
// Read frame
|
|
ret = i2c_master_read_from_device(NFC_I2C_NUM, NFC_I2C_ADDR,
|
|
frame, 255, pdMS_TO_TICKS(100));
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
// Skip RDY byte
|
|
size_t idx = 1;
|
|
|
|
// Check preamble
|
|
if (frame[idx++] != PN532_PREAMBLE ||
|
|
frame[idx++] != PN532_STARTCODE1 ||
|
|
frame[idx++] != PN532_STARTCODE2) {
|
|
return ESP_ERR_INVALID_RESPONSE;
|
|
}
|
|
|
|
// Get length
|
|
uint8_t len = frame[idx++];
|
|
uint8_t len_checksum = frame[idx++];
|
|
|
|
if ((len + len_checksum) != 0) {
|
|
return ESP_ERR_INVALID_RESPONSE;
|
|
}
|
|
|
|
// Check TFI
|
|
if (frame[idx++] != PN532_PN532TOHOST) {
|
|
return ESP_ERR_INVALID_RESPONSE;
|
|
}
|
|
|
|
// Copy response data
|
|
*response_len = len - 1;
|
|
memcpy(response, &frame[idx], *response_len);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Get PN532 firmware version
|
|
*/
|
|
static esp_err_t pn532_get_firmware_version(uint32_t *version)
|
|
{
|
|
uint8_t cmd[] = {PN532_COMMAND_GETFIRMWAREVERSION};
|
|
uint8_t response[32];
|
|
size_t response_len;
|
|
|
|
ESP_ERROR_CHECK(pn532_write_command(cmd, sizeof(cmd)));
|
|
ESP_ERROR_CHECK(pn532_read_response(response, &response_len, 1000));
|
|
|
|
if (response_len < 5 || response[0] != (PN532_COMMAND_GETFIRMWAREVERSION + 1)) {
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
*version = (response[1] << 24) | (response[2] << 16) |
|
|
(response[3] << 8) | response[4];
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Configure PN532 SAM (Security Access Module)
|
|
*/
|
|
static esp_err_t pn532_sam_configuration(void)
|
|
{
|
|
uint8_t cmd[] = {
|
|
PN532_COMMAND_SAMCONFIGURATION,
|
|
0x01, // Normal mode
|
|
0x14, // Timeout 50ms * 20 = 1 second
|
|
0x01 // Use IRQ pin
|
|
};
|
|
uint8_t response[32];
|
|
size_t response_len;
|
|
|
|
ESP_ERROR_CHECK(pn532_write_command(cmd, sizeof(cmd)));
|
|
ESP_ERROR_CHECK(pn532_read_response(response, &response_len, 1000));
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Detect passive NFC target (tag)
|
|
*/
|
|
static esp_err_t pn532_read_passive_target(uint8_t *uid, uint8_t *uid_len)
|
|
{
|
|
uint8_t cmd[] = {
|
|
PN532_COMMAND_INLISTPASSIVETARGET,
|
|
0x01, // Max 1 card
|
|
0x00 // 106 kbps type A (ISO14443A)
|
|
};
|
|
uint8_t response[32];
|
|
size_t response_len;
|
|
|
|
esp_err_t ret = pn532_write_command(cmd, sizeof(cmd));
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
ret = pn532_read_response(response, &response_len, 1000);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
// Check if tag found
|
|
if (response_len < 8 || response[0] != (PN532_COMMAND_INLISTPASSIVETARGET + 1)) {
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
uint8_t num_tags = response[1];
|
|
if (num_tags != 1) {
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
// Get UID length
|
|
*uid_len = response[6];
|
|
if (*uid_len > 7) {
|
|
*uid_len = 7;
|
|
}
|
|
|
|
// Copy UID
|
|
memcpy(uid, &response[7], *uid_len);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Load ROM mappings from JSON file
|
|
*/
|
|
static esp_err_t nfc_load_mappings(void)
|
|
{
|
|
FILE *f = fopen("/sdcard/nfc_roms.json", "r");
|
|
if (f == NULL) {
|
|
ESP_LOGW(TAG, "nfc_roms.json not found, using empty mappings");
|
|
return ESP_OK;
|
|
}
|
|
|
|
// Read file
|
|
fseek(f, 0, SEEK_END);
|
|
long fsize = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
char *json_str = malloc(fsize + 1);
|
|
if (json_str == NULL) {
|
|
fclose(f);
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
fread(json_str, 1, fsize, f);
|
|
json_str[fsize] = 0;
|
|
fclose(f);
|
|
|
|
// Parse JSON
|
|
cJSON *root = cJSON_Parse(json_str);
|
|
free(json_str);
|
|
|
|
if (root == NULL) {
|
|
ESP_LOGE(TAG, "Failed to parse nfc_roms.json");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
cJSON *mappings_array = cJSON_GetObjectItem(root, "mappings");
|
|
if (!cJSON_IsArray(mappings_array)) {
|
|
cJSON_Delete(root);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
// Load mappings
|
|
rom_mapping_count = 0;
|
|
cJSON *mapping = NULL;
|
|
cJSON_ArrayForEach(mapping, mappings_array) {
|
|
if (rom_mapping_count >= MAX_ROM_MAPPINGS) {
|
|
break;
|
|
}
|
|
|
|
cJSON *uid_str = cJSON_GetObjectItem(mapping, "tag_uid");
|
|
cJSON *rom_path = cJSON_GetObjectItem(mapping, "rom_path");
|
|
|
|
if (cJSON_IsString(uid_str) && cJSON_IsString(rom_path)) {
|
|
// Parse UID (format: "04:12:34:56:78:9A:B0")
|
|
const char *uid = uid_str->valuestring;
|
|
uint8_t uid_bytes[7] = {0};
|
|
int uid_len = 0;
|
|
|
|
for (int i = 0; i < 7 && *uid; i++) {
|
|
sscanf(uid, "%02hhx", &uid_bytes[i]);
|
|
uid_len++;
|
|
uid += 2;
|
|
if (*uid == ':') uid++;
|
|
}
|
|
|
|
// Store mapping
|
|
memcpy(rom_mappings[rom_mapping_count].tag_uid, uid_bytes, 7);
|
|
strncpy(rom_mappings[rom_mapping_count].rom_path,
|
|
rom_path->valuestring, NFC_MAX_ROM_NAME - 1);
|
|
rom_mapping_count++;
|
|
}
|
|
}
|
|
|
|
cJSON_Delete(root);
|
|
ESP_LOGI(TAG, "Loaded %d ROM mappings", rom_mapping_count);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Find ROM path for given UID
|
|
*/
|
|
static const char* nfc_find_rom_for_uid(const uint8_t *uid, uint8_t uid_len)
|
|
{
|
|
for (int i = 0; i < rom_mapping_count; i++) {
|
|
if (memcmp(rom_mappings[i].tag_uid, uid, uid_len) == 0) {
|
|
return rom_mappings[i].rom_path;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// ===========================================
|
|
// Public API
|
|
// ===========================================
|
|
|
|
esp_err_t nfc_manager_init(void)
|
|
{
|
|
ESP_LOGI(TAG, "Initializing NFC Manager...");
|
|
|
|
// Initialize I2C
|
|
ESP_ERROR_CHECK(pn532_i2c_init());
|
|
|
|
// Wait a bit for PN532 to wake up
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
|
|
// Get firmware version
|
|
uint32_t version;
|
|
esp_err_t ret = pn532_get_firmware_version(&version);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to communicate with PN532!");
|
|
return ret;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "PN532 Firmware v%d.%d",
|
|
(version >> 16) & 0xFF, (version >> 8) & 0xFF);
|
|
|
|
// Configure SAM
|
|
ESP_ERROR_CHECK(pn532_sam_configuration());
|
|
|
|
// Load ROM mappings
|
|
nfc_load_mappings();
|
|
|
|
ESP_LOGI(TAG, "✓ NFC Manager initialized");
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
bool nfc_manager_tag_present(void)
|
|
{
|
|
uint8_t uid[7];
|
|
uint8_t uid_len;
|
|
|
|
return (pn532_read_passive_target(uid, &uid_len) == ESP_OK);
|
|
}
|
|
|
|
esp_err_t nfc_manager_get_rom(char *rom_path, size_t max_len)
|
|
{
|
|
uint8_t uid[7];
|
|
uint8_t uid_len;
|
|
|
|
// Read tag
|
|
esp_err_t ret = pn532_read_passive_target(uid, &uid_len);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
// Log UID
|
|
ESP_LOGI(TAG, "Tag UID: %02X:%02X:%02X:%02X:%02X:%02X:%02X",
|
|
uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6]);
|
|
|
|
// Find ROM mapping
|
|
const char *mapped_rom = nfc_find_rom_for_uid(uid, uid_len);
|
|
if (mapped_rom == NULL) {
|
|
ESP_LOGW(TAG, "No ROM mapping found for this tag");
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
// Copy ROM path
|
|
strncpy(rom_path, mapped_rom, max_len - 1);
|
|
rom_path[max_len - 1] = '\0';
|
|
|
|
ESP_LOGI(TAG, "ROM: %s", rom_path);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|