From fc05fa2f8b4900c1ff729636cb1c397e3d6320bd Mon Sep 17 00:00:00 2001 From: Rasmus Date: Sat, 12 Sep 2026 16:43:02 +0200 Subject: [PATCH] fix(flash): increase INT_WDT timeout, enable littlefs wdt reset and thread safety --- main/scryfall_client.c | 15 +++++++++--- main/storage_manager.c | 54 +++++++++++++++++++++++++++++++++++++++++- sdkconfig | 14 +++++------ sdkconfig.defaults | 3 ++- 4 files changed, 74 insertions(+), 12 deletions(-) diff --git a/main/scryfall_client.c b/main/scryfall_client.c index ee4c8d6..d3f90ad 100644 --- a/main/scryfall_client.c +++ b/main/scryfall_client.c @@ -224,7 +224,9 @@ static esp_err_t http_get_json(const char *url, char **out_buf) static esp_err_t http_download_file(const char *url, const char *path) { - FILE *f = fopen(path, "wb"); + char tmp[128]; + snprintf(tmp, sizeof(tmp), "%s.tmp", path); + FILE *f = fopen(tmp, "wb"); if (f == NULL) { return ESP_FAIL; } @@ -232,9 +234,14 @@ static esp_err_t http_download_file(const char *url, const char *path) esp_err_t ret = http_request(url, &sink); fclose(f); if (ret != ESP_OK) { - remove(path); + remove(tmp); return ret; } + remove(path); + if (rename(tmp, path) != 0) { + remove(tmp); + return ESP_FAIL; + } return ESP_OK; } @@ -397,9 +404,11 @@ static esp_err_t scryfall_do_fetch(const char *card_name, const char *uid) if (image_url && strlen(image_url) > 0) { char jpg_path[64]; snprintf(jpg_path, sizeof(jpg_path), STORAGE_CARDS_DIR "/%s.jpg", uid); + char enc_img[512]; + url_encode(image_url, enc_img, sizeof(enc_img)); char proxy_url[600]; /* wsrv.nl converts Scryfall's progressive JPEG to a lightweight 200x110 baseline JPEG */ - snprintf(proxy_url, sizeof(proxy_url), "https://wsrv.nl/?url=%s&w=200&h=110&fit=cover&output=jpg", image_url); + snprintf(proxy_url, sizeof(proxy_url), "https://wsrv.nl/?url=%s&w=200&h=110&fit=cover&output=jpg", enc_img); esp_err_t dl = http_download_file(proxy_url, jpg_path); if (dl != ESP_OK) { ESP_LOGW(TAG, "proxied image download failed, trying direct: %s", image_url); diff --git a/main/storage_manager.c b/main/storage_manager.c index 04ba25e..f78ee90 100644 --- a/main/storage_manager.c +++ b/main/storage_manager.c @@ -8,6 +8,8 @@ #include #include #include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #include "esp_log.h" #include "esp_littlefs.h" #include "cJSON.h" @@ -17,11 +19,32 @@ static const char *TAG = "storage"; #define STORAGE_PARTITION_LABEL "storage" +static SemaphoreHandle_t s_storage_mux = NULL; + +static void storage_lock(void) +{ + if (s_storage_mux == NULL) { + s_storage_mux = xSemaphoreCreateRecursiveMutex(); + } + if (s_storage_mux != NULL) { + xSemaphoreTakeRecursive(s_storage_mux, portMAX_DELAY); + } +} + +static void storage_unlock(void) +{ + if (s_storage_mux != NULL) { + xSemaphoreGiveRecursive(s_storage_mux); + } +} bool storage_file_exists(const char *path) { + storage_lock(); struct stat st; - return (stat(path, &st) == 0) && S_ISREG(st.st_mode); + bool exists = (stat(path, &st) == 0) && S_ISREG(st.st_mode); + storage_unlock(); + return exists; } esp_err_t storage_read_text(const char *path, char **out_buf, size_t *out_len) @@ -29,21 +52,25 @@ esp_err_t storage_read_text(const char *path, char **out_buf, size_t *out_len) if (out_buf == NULL || out_len == NULL) { return ESP_ERR_INVALID_ARG; } + storage_lock(); *out_buf = NULL; *out_len = 0; FILE *f = fopen(path, "rb"); if (f == NULL) { + storage_unlock(); return ESP_ERR_NOT_FOUND; } if (fseek(f, 0, SEEK_END) != 0) { fclose(f); + storage_unlock(); return ESP_FAIL; } long size = ftell(f); if (size < 0) { fclose(f); + storage_unlock(); return ESP_FAIL; } rewind(f); @@ -51,6 +78,7 @@ esp_err_t storage_read_text(const char *path, char **out_buf, size_t *out_len) char *buf = malloc((size_t)size + 1); if (buf == NULL) { fclose(f); + storage_unlock(); return ESP_ERR_NO_MEM; } @@ -58,11 +86,13 @@ esp_err_t storage_read_text(const char *path, char **out_buf, size_t *out_len) fclose(f); if (rd != (size_t)size) { free(buf); + storage_unlock(); return ESP_FAIL; } buf[rd] = '\0'; *out_buf = buf; *out_len = rd; + storage_unlock(); return ESP_OK; } @@ -76,27 +106,34 @@ esp_err_t storage_write_text(const char *path, const char *text) } snprintf(tmp, sizeof(tmp), "%s.tmp", path); + storage_lock(); FILE *f = fopen(tmp, "wb"); if (f == NULL) { ESP_LOGE(TAG, "open %s failed", tmp); + storage_unlock(); return ESP_FAIL; } size_t wr = fwrite(text, 1, strlen(text), f); if (wr != strlen(text) || fflush(f) != 0 || fclose(f) != 0) { ESP_LOGE(TAG, "write %s failed", tmp); remove(tmp); + storage_unlock(); return ESP_FAIL; } + remove(path); if (rename(tmp, path) != 0) { ESP_LOGE(TAG, "rename %s -> %s failed", tmp, path); remove(tmp); + storage_unlock(); return ESP_FAIL; } + storage_unlock(); return ESP_OK; } esp_err_t storage_manager_bind_uid(const char *uid, const char *card_name) { + storage_lock(); cJSON *root = NULL; char *existing = NULL; size_t existing_len = 0; @@ -111,6 +148,7 @@ esp_err_t storage_manager_bind_uid(const char *uid, const char *card_name) root = cJSON_CreateObject(); } if (root == NULL) { + storage_unlock(); return ESP_ERR_NO_MEM; } @@ -133,6 +171,7 @@ esp_err_t storage_manager_bind_uid(const char *uid, const char *card_name) esp_err_t ret = out ? storage_write_text(STORAGE_MAPPINGS, out) : ESP_ERR_NO_MEM; free(out); cJSON_Delete(root); + storage_unlock(); return ret; } @@ -141,6 +180,7 @@ esp_err_t storage_manager_unbind_uid(const char *uid) if (uid == NULL || uid[0] == '\0') { return ESP_ERR_INVALID_ARG; } + storage_lock(); char *existing = NULL; size_t existing_len = 0; cJSON *root = NULL; @@ -152,11 +192,13 @@ esp_err_t storage_manager_unbind_uid(const char *uid) if (root == NULL || !cJSON_IsObject(root)) { if (root) cJSON_Delete(root); + storage_unlock(); return ESP_ERR_NOT_FOUND; } if (!cJSON_HasObjectItem(root, uid)) { cJSON_Delete(root); + storage_unlock(); return ESP_ERR_NOT_FOUND; } @@ -174,16 +216,19 @@ esp_err_t storage_manager_unbind_uid(const char *uid) esp_err_t ret = out ? storage_write_text(STORAGE_MAPPINGS, out) : ESP_ERR_NO_MEM; free(out); cJSON_Delete(root); + storage_unlock(); return ret; } esp_err_t storage_manager_lookup_name(const char *uid, char **out) { *out = NULL; + storage_lock(); char *buf = NULL; size_t len = 0; if (storage_read_text(STORAGE_MAPPINGS, &buf, &len) != ESP_OK || buf == NULL) { free(buf); + storage_unlock(); return ESP_ERR_NOT_FOUND; } @@ -191,6 +236,7 @@ esp_err_t storage_manager_lookup_name(const char *uid, char **out) free(buf); if (root == NULL || !cJSON_IsObject(root)) { if (root) cJSON_Delete(root); + storage_unlock(); return ESP_ERR_NOT_FOUND; } @@ -203,6 +249,7 @@ esp_err_t storage_manager_lookup_name(const char *uid, char **out) } } cJSON_Delete(root); + storage_unlock(); return ret; } @@ -269,8 +316,10 @@ esp_err_t storage_manager_count_cached(int *json_count, int *jpg_count) *json_count = 0; *jpg_count = 0; + storage_lock(); DIR *d = opendir(STORAGE_CARDS_DIR); if (d == NULL) { + storage_unlock(); return ESP_OK; } struct dirent *entry; @@ -285,11 +334,13 @@ esp_err_t storage_manager_count_cached(int *json_count, int *jpg_count) } } closedir(d); + storage_unlock(); return ESP_OK; } esp_err_t storage_manager_clear_all_cards(bool reset_mappings) { + storage_lock(); DIR *d = opendir(STORAGE_CARDS_DIR); if (d != NULL) { struct dirent *entry; @@ -309,5 +360,6 @@ esp_err_t storage_manager_clear_all_cards(bool reset_mappings) storage_write_text(STORAGE_MAPPINGS, "{\n}\n"); ESP_LOGI(TAG, "Reset %s to empty", STORAGE_MAPPINGS); } + storage_unlock(); return ESP_OK; } \ No newline at end of file diff --git a/sdkconfig b/sdkconfig index 3a5b93e..88d078c 100644 --- a/sdkconfig +++ b/sdkconfig @@ -1148,12 +1148,12 @@ CONFIG_ESP_CONSOLE_UART_NUM=0 CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 CONFIG_ESP_INT_WDT=y -CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_INT_WDT_TIMEOUT_MS=2000 CONFIG_ESP_INT_WDT_CHECK_CPU1=y CONFIG_ESP_TASK_WDT_EN=y CONFIG_ESP_TASK_WDT_INIT=y # CONFIG_ESP_TASK_WDT_PANIC is not set -CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_TIMEOUT_S=15 CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y # CONFIG_ESP_PANIC_HANDLER_IRAM is not set @@ -1921,10 +1921,10 @@ CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y # CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set # CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set # CONFIG_SPI_FLASH_SHARE_SPI1_BUS is not set -# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set +CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE=y CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 -CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 +CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=2 CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 # CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set # CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set @@ -2114,7 +2114,7 @@ CONFIG_LITTLEFS_MALLOC_STRATEGY_DEFAULT=y # CONFIG_LITTLEFS_MALLOC_STRATEGY_SPIRAM_PREFER is not set CONFIG_LITTLEFS_ASSERTS=y # CONFIG_LITTLEFS_MMAP_PARTITION is not set -# CONFIG_LITTLEFS_WDT_RESET is not set +CONFIG_LITTLEFS_WDT_RESET=y # end of LittleFS # @@ -2541,12 +2541,12 @@ CONFIG_CONSOLE_UART=y CONFIG_CONSOLE_UART_NUM=0 CONFIG_CONSOLE_UART_BAUDRATE=115200 CONFIG_INT_WDT=y -CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_TIMEOUT_MS=2000 CONFIG_INT_WDT_CHECK_CPU1=y CONFIG_TASK_WDT=y CONFIG_ESP_TASK_WDT=y # CONFIG_TASK_WDT_PANIC is not set -CONFIG_TASK_WDT_TIMEOUT_S=5 +CONFIG_TASK_WDT_TIMEOUT_S=15 CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y # CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 955d120..0b35200 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -35,8 +35,9 @@ CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=2 # Watchdog Timeouts for Flash Erase & OTA Operations -CONFIG_ESP_INT_WDT_TIMEOUT_MS=1000 +CONFIG_ESP_INT_WDT_TIMEOUT_MS=2000 CONFIG_ESP_TASK_WDT_TIMEOUT_S=15 +CONFIG_LITTLEFS_WDT_RESET=y # mbedTLS Dynamic Buffers & Memory Optimization (Vital for ESP32 without PSRAM) CONFIG_MBEDTLS_DYNAMIC_BUFFER=y