#include "nfc_engine/nfc_engine.h" #include "pn532_host/pn532_core.h" #include "esp_log.h" #include #include #include static const char *TAG = "nfc_engine"; static uint8_t s_tg = 1; static void hint_type(nfc_tag_info_t *t) { switch (t->sak) { case 0x08: case 0x09: case 0x18: t->type_hint = 1; break; case 0x00: t->type_hint = 2; break; default: t->type_hint = 0; break; } } bool nfc_tag_is_mifare_classic(const nfc_tag_info_t *tag) { return tag && tag->type_hint == 1; } bool nfc_tag_is_mifare_classic_4k(const nfc_tag_info_t *tag) { return nfc_tag_is_mifare_classic(tag) && tag->sak == 0x18; } bool nfc_tag_is_type2(const nfc_tag_info_t *tag) { return tag && tag->type_hint == 2; } esp_err_t nfc_engine_init(void) { esp_err_t e = pn532_core_init(); if (e != ESP_OK) { return e; } e = pn532_sam_config_normal(); if (e != ESP_OK) { ESP_LOGW(TAG, "SAM config: %s", esp_err_to_name(e)); } uint8_t ic = 0, hi = 0, lo = 0; if (pn532_get_firmware_version(&ic, &hi, &lo) == ESP_OK) { ESP_LOGI(TAG, "PN532 fw ic=0x%02x %u.%u", ic, hi, lo); } if (pn532_rf_max_retries() != ESP_OK) { ESP_LOGW(TAG, "RF max retries config failed"); } return ESP_OK; } esp_err_t nfc_poll_passive_target(nfc_tag_info_t *out) { if (!out) { return ESP_ERR_INVALID_ARG; } memset(out, 0, sizeof(*out)); uint8_t resp[64]; size_t rlen = 0; esp_err_t e = pn532_in_list_passive_target(1, 0x00, resp, sizeof(resp), &rlen); if (e != ESP_OK) { return e; } if (rlen < 2) { return ESP_ERR_INVALID_RESPONSE; } if (resp[0] < 1) { return ESP_ERR_NOT_FOUND; } if (rlen < 7) { return ESP_ERR_INVALID_RESPONSE; } s_tg = resp[1]; out->atqa = (uint16_t)(((uint16_t)resp[2] << 8) | resp[3]); out->sak = resp[4]; out->uid_len = resp[5]; if (out->uid_len > NFC_MAX_UID_LEN || (size_t)(6 + out->uid_len) > rlen) { return ESP_ERR_INVALID_RESPONSE; } memcpy(out->uid, resp + 6, out->uid_len); hint_type(out); return ESP_OK; } static esp_err_t in_data_tg(const uint8_t *data, size_t data_len, uint8_t *response, size_t response_max, size_t *response_len) { uint8_t buf[64]; if (!data || !response || !response_len || data_len == 0 || data_len > sizeof(buf) - 2) { return ESP_ERR_INVALID_SIZE; } buf[0] = PN532_CMD_INDATAEXCHANGE; buf[1] = s_tg; memcpy(buf + 2, data, data_len); return pn532_send_cmd(buf, 2 + data_len, response, response_max, response_len, 500); } esp_err_t nfc_mifare_authenticate_block(const nfc_tag_info_t *tag, uint8_t block_no, const nfc_mifare_key_t *key) { if (!tag || !key) { return ESP_ERR_INVALID_ARG; } uint8_t data[12]; data[0] = key->key_b ? PN532_MIFARE_CMD_AUTH_B : PN532_MIFARE_CMD_AUTH_A; data[1] = block_no; memcpy(data + 2, key->key, 6); uint8_t uid_use[4]; if (tag->uid_len == 4) { memcpy(uid_use, tag->uid, 4); } else if (tag->uid_len >= 7) { memcpy(uid_use, tag->uid + tag->uid_len - 4, 4); } else if (tag->uid_len > 4) { memcpy(uid_use, tag->uid + tag->uid_len - 4, 4); } else { return ESP_ERR_INVALID_ARG; } memcpy(data + 8, uid_use, 4); uint8_t resp[32]; size_t rlen = 0; esp_err_t e = in_data_tg(data, sizeof(data), resp, sizeof(resp), &rlen); if (e != ESP_OK) { return e; } if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) { return ESP_FAIL; } return ESP_OK; } esp_err_t nfc_mifare_read_block(uint8_t block_no, uint8_t block[NFC_BLOCK_LEN]) { if (!block) { return ESP_ERR_INVALID_ARG; } uint8_t d[] = {PN532_MIFARE_CMD_READ, block_no}; uint8_t resp[32]; size_t rlen = 0; esp_err_t e = in_data_tg(d, sizeof(d), resp, sizeof(resp), &rlen); if (e != ESP_OK) { return e; } if (rlen < 2 + NFC_BLOCK_LEN || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) { return ESP_ERR_INVALID_RESPONSE; } memcpy(block, resp + 2, NFC_BLOCK_LEN); return ESP_OK; } esp_err_t nfc_mifare_write_block(uint8_t block_no, const uint8_t block[NFC_BLOCK_LEN]) { if (!block) { return ESP_ERR_INVALID_ARG; } uint8_t d[2 + NFC_BLOCK_LEN]; d[0] = PN532_MIFARE_CMD_WRITE; d[1] = block_no; memcpy(d + 2, block, NFC_BLOCK_LEN); uint8_t resp[16]; size_t rlen = 0; esp_err_t e = in_data_tg(d, sizeof(d), resp, sizeof(resp), &rlen); if (e != ESP_OK) { return e; } if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) { return ESP_FAIL; } return ESP_OK; } esp_err_t nfc_ultralight_read_page(uint8_t page, uint8_t data[4]) { if (!data) { return ESP_ERR_INVALID_ARG; } uint8_t d[] = {PN532_MIFARE_CMD_READ, page}; uint8_t resp[32]; size_t rlen = 0; esp_err_t e = in_data_tg(d, sizeof(d), resp, sizeof(resp), &rlen); if (e != ESP_OK) { return e; } if (rlen < 2 + 16 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) { return ESP_ERR_INVALID_RESPONSE; } memcpy(data, resp + 2, 4); return ESP_OK; } esp_err_t nfc_ultralight_write_page(uint8_t page, const uint8_t data[4]) { if (!data) { return ESP_ERR_INVALID_ARG; } uint8_t d[6] = {0xA2, page, data[0], data[1], data[2], data[3]}; uint8_t resp[16]; size_t rlen = 0; esp_err_t e = in_data_tg(d, sizeof(d), resp, sizeof(resp), &rlen); if (e != ESP_OK) { return e; } if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) { return ESP_FAIL; } return ESP_OK; } esp_err_t nfc_ul_fast_read(uint8_t start_page, uint8_t *out, size_t out_max, size_t *got) { if (!out || !got || out_max == 0) { return ESP_ERR_INVALID_ARG; } *got = 0; size_t max_pages = MAX((size_t)1, MIN((size_t)16, out_max / 4)); size_t remaining_pages = 256U - (size_t)start_page; if (max_pages > remaining_pages) { max_pages = remaining_pages; } uint8_t end_page = (uint8_t)(start_page + max_pages - 1); uint8_t d[3] = {0x3A, start_page, end_page}; uint8_t resp[256]; size_t rlen = 0; esp_err_t e = in_data_tg(d, sizeof(d), resp, sizeof(resp), &rlen); if (e != ESP_OK) { return e; } if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) { return ESP_ERR_INVALID_RESPONSE; } size_t payload = rlen - 2; if (payload > out_max) { payload = out_max; } memcpy(out, resp + 2, payload); *got = payload; return ESP_OK; } cJSON *nfc_tag_to_json(const nfc_tag_info_t *tag) { if (!tag || tag->uid_len > NFC_MAX_UID_LEN) { return NULL; } cJSON *o = cJSON_CreateObject(); if (!o) { return NULL; } char uidhex[NFC_MAX_UID_LEN * 2 + 4]; for (int i = 0; i < tag->uid_len; i++) { snprintf(uidhex + i * 2, 3, "%02X", tag->uid[i]); } uidhex[tag->uid_len * 2] = 0; cJSON_AddStringToObject(o, "uid", uidhex); cJSON_AddNumberToObject(o, "uidLen", tag->uid_len); cJSON_AddNumberToObject(o, "atqa", tag->atqa); cJSON_AddNumberToObject(o, "sak", tag->sak); cJSON_AddNumberToObject(o, "typeHint", tag->type_hint); char aq[8]; snprintf(aq, sizeof aq, "%04X", (unsigned)tag->atqa); cJSON_AddStringToObject(o, "atqaHex", aq); char sk[8]; snprintf(sk, sizeof sk, "%02X", tag->sak); cJSON_AddStringToObject(o, "sakHex", sk); const char *guess = "Unknown / use Raw or RATS"; if (nfc_tag_is_mifare_classic(tag)) { guess = nfc_tag_is_mifare_classic_4k(tag) ? "MIFARE Classic 4K" : "MIFARE Classic 1K or compatible"; } else if (nfc_tag_is_type2(tag)) { guess = "Ultralight / NTAG / Type 2 family"; } cJSON_AddStringToObject(o, "typeGuess", guess); uint8_t gs[32]; size_t gl = 0; if (pn532_get_general_status(gs, sizeof(gs), &gl) == ESP_OK && gl > 0) { cJSON *arr = cJSON_CreateArray(); if (arr) { for (size_t i = 0; i < gl; i++) { cJSON_AddItemToArray(arr, cJSON_CreateNumber(gs[i])); } cJSON_AddItemToObject(o, "pn532GeneralStatus", arr); } } return o; }