fix(flash): increase INT_WDT timeout, enable littlefs wdt reset and thread safety
All checks were successful
ESP32 Build & Release / build (push) Successful in 1m11s

This commit is contained in:
2026-09-12 16:43:02 +02:00
parent 4a5b058c1a
commit fc05fa2f8b
4 changed files with 74 additions and 12 deletions

View File

@@ -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);

View File

@@ -8,6 +8,8 @@
#include <sys/stat.h>
#include <sys/unistd.h>
#include <dirent.h>
#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;
}

View File

@@ -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

View File

@@ -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