feat: resilient boot + full polish pass

Firmware:
- nfc_engine: add nfc_engine_try_init() (non-fatal, sets s_pn532_ready),
  nfc_engine_try_reattach() (soft re-init, skips bus re-init),
  nfc_engine_is_ready() accessor
- main: replace ESP_ERROR_CHECK(nfc_engine_init) with nfc_engine_try_init;
  device boots and serves web UI even with no PN532 connected
- app_net: add reconnect_task — every 5s retries nfc_engine_try_reattach()
  and broadcasts {"channel":"pn532","payload":{"connected":true}} over WS
- app_net: scan_loop_task skips polling when !nfc_engine_is_ready()
- app_net/api_status: always emit pn532Connected bool; null-guard pn532 fw object
- find_sector_hit / program_classic_snapshot_locked: null-guard cJSON array items
- session_capture: abort if xSemaphoreCreateMutex() returns NULL

Web:
- NfcWsContext: track pn532Connected state from WS pn532 channel + status fetch on connect
- App.tsx: live HeaderBadge (LIVE/NO RF/WAIT) replacing static text
- Dashboard: READY/SEARCHING pill with fw version when available
- api.ts: add pn532Connected to Status type
- toast.tsx: fix ID collision (Date.now + Math.random)
- Capture: surface status fetch errors
- ReadAnalyze: add error feedback for readUl when no data returned
- WriteClone: busy state on both write buttons
- RawConsole: toast when frame returns error not response
- Emulate: validate hex before send (non-empty, even length, hex chars only)
- Brute: warn and skip invalid custom key lines

Made-with: Cursor
This commit is contained in:
drjones
2026-04-09 15:49:18 -07:00
parent 63db10d400
commit 2da1515f8d
20 changed files with 236 additions and 53 deletions

View File

@@ -368,6 +368,7 @@ static cJSON *find_sector_hit(const cJSON *attack, int sector)
int n = cJSON_GetArraySize(hits);
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(hits, i);
if (!it) continue;
cJSON *s = cJSON_GetObjectItemCaseSensitive(it, "sector");
if (cJSON_IsNumber(s) && (int)cJSON_GetNumberValue(s) == sector) {
return it;
@@ -587,6 +588,7 @@ static cJSON *program_classic_snapshot_locked(const nfc_tag_info_t *tag, const c
int sectors_n = cJSON_GetArraySize(sectors);
for (int si = 0; si < sectors_n; si++) {
cJSON *sec = cJSON_GetArrayItem(sectors, si);
if (!sec) { skipped++; continue; }
cJSON *blocks = cJSON_GetObjectItemCaseSensitive(sec, "blocksHex");
cJSON *first = cJSON_GetObjectItemCaseSensitive(sec, "firstBlock");
cJSON *count = cJSON_GetObjectItemCaseSensitive(sec, "blockCount");
@@ -661,16 +663,22 @@ static esp_err_t api_status(httpd_req_t *req)
wifi_mode_t mode;
esp_wifi_get_mode(&mode);
cJSON_AddNumberToObject(o, "wifiMode", mode);
uint8_t ic = 0, hi = 0, lo = 0;
nfc_access_lock();
if (pn532_get_firmware_version(&ic, &hi, &lo) == ESP_OK) {
cJSON *pn = cJSON_CreateObject();
cJSON_AddNumberToObject(pn, "ic", ic);
cJSON_AddNumberToObject(pn, "fwHi", hi);
cJSON_AddNumberToObject(pn, "fwLo", lo);
cJSON_AddItemToObject(o, "pn532", pn);
bool pn532_ok = nfc_engine_is_ready();
cJSON_AddBoolToObject(o, "pn532Connected", pn532_ok);
if (pn532_ok) {
uint8_t ic = 0, hi = 0, lo = 0;
nfc_access_lock();
if (pn532_get_firmware_version(&ic, &hi, &lo) == ESP_OK) {
cJSON *pn = cJSON_CreateObject();
if (pn) {
cJSON_AddNumberToObject(pn, "ic", ic);
cJSON_AddNumberToObject(pn, "fwHi", hi);
cJSON_AddNumberToObject(pn, "fwLo", lo);
cJSON_AddItemToObject(o, "pn532", pn);
}
}
nfc_access_unlock();
}
nfc_access_unlock();
cJSON_AddBoolToObject(o, "scanning", s_scan);
cJSON_AddBoolToObject(o, "targetActive", s_target_active);
size_t cap_u = 0;
@@ -1710,12 +1718,33 @@ static esp_err_t ws_handler(httpd_req_t *req)
return ESP_OK;
}
static void reconnect_task(void *arg)
{
(void)arg;
while (1) {
vTaskDelay(pdMS_TO_TICKS(5000));
if (!nfc_engine_is_ready()) {
nfc_access_lock();
bool ok = nfc_engine_try_reattach();
nfc_access_unlock();
if (ok) {
ESP_LOGI(TAG, "PN532 reattached — broadcasting status");
app_net_broadcast_json("pn532", "{\"connected\":true}");
}
}
}
}
static void scan_loop_task(void *arg)
{
(void)arg;
nfc_tag_info_t last;
memset(&last, 0, sizeof(last));
while (1) {
if (!nfc_engine_is_ready()) {
vTaskDelay(pdMS_TO_TICKS(500));
continue;
}
if (!s_scan) {
vTaskDelay(pdMS_TO_TICKS(200));
continue;
@@ -2049,5 +2078,8 @@ esp_err_t app_net_init(void)
s_server = NULL;
return ESP_ERR_NO_MEM;
}
if (xTaskCreate(reconnect_task, "pn532_reconnect", 4096, NULL, 3, NULL) != pdPASS) {
ESP_LOGW(TAG, "reconnect task create failed — hot-plug retry disabled");
}
return ESP_OK;
}