From ca924ec70059a283db3a002fe4171f20d4e97a28 Mon Sep 17 00:00:00 2001 From: craigmillard86 Date: Sat, 17 Jan 2026 18:08:49 +0000 Subject: [PATCH 1/2] feat: Add WiFi resilience settings with Advanced Settings UI Add configurable parameters to improve audio streaming stability on congested WiFi networks: - TCP_NODELAY: Disable Nagle's algorithm for lower latency - Queue empty hysteresis: Require consecutive empty reads before hard resync - Queue insert timeout: Configurable wait time for queue space - Fast sync tolerance: Latency buffer for sync operations - Reconnect delays: Exponential backoff with jitter for reconnection - Buffer headroom: Extra capacity for WiFi jitter tolerance All parameters are configurable via: - menuconfig (compile-time defaults) - New "Advanced Settings" Web UI tab (runtime with NVS persistence) --- components/lightsnapcast/Kconfig.projbuild | 29 ++ components/lightsnapcast/player.c | 32 +- .../include/settings_manager.h | 37 ++ .../settings_manager/settings_manager.c | 469 ++++++++++++++++++ components/ui_http_server/CMakeLists.txt | 1 + .../html/advanced-settings.html | 367 ++++++++++++++ components/ui_http_server/html/index.html | 1 + components/ui_http_server/ui_http_server.c | 231 ++++++++- main/Kconfig.projbuild | 34 ++ main/main.c | 40 +- 10 files changed, 1214 insertions(+), 27 deletions(-) create mode 100644 components/lightsnapcast/Kconfig.projbuild create mode 100644 components/ui_http_server/html/advanced-settings.html diff --git a/components/lightsnapcast/Kconfig.projbuild b/components/lightsnapcast/Kconfig.projbuild new file mode 100644 index 00000000..8d1ab6d3 --- /dev/null +++ b/components/lightsnapcast/Kconfig.projbuild @@ -0,0 +1,29 @@ +menu "Lightsnapcast Player Settings" + config PLAYER_QUEUE_EMPTY_THRESHOLD + int "Queue empty threshold for hard resync" + range 1 10 + default 3 + help + Number of consecutive empty queue reads before triggering hard resync. + Higher values tolerate brief WiFi dropouts better. + Default: 3 (covers ~78ms of delay) + + config PLAYER_QUEUE_INSERT_TIMEOUT_MS + int "Queue insert timeout (ms)" + range 1 200 + default 50 + help + Timeout for inserting audio chunks into the playback queue. + Higher values allow queue to drain during burst arrivals. + Default: 50ms + + config PLAYER_BUFFER_HEADROOM_PCT + int "Buffer headroom percentage" + range 0 100 + default 50 if SPIRAM && SPIRAM_BOOT_INIT + default 20 + help + Extra buffer capacity as percentage of base requirement. + Provides headroom for WiFi jitter. Use higher values with PSRAM. + Default: 50% with PSRAM, 20% without +endmenu diff --git a/components/lightsnapcast/player.c b/components/lightsnapcast/player.c index 6f0c8585..9821d731 100644 --- a/components/lightsnapcast/player.c +++ b/components/lightsnapcast/player.c @@ -1196,8 +1196,7 @@ int32_t insert_pcm_chunk(pcm_chunk_message_t *pcmChunk) { // free_pcm_chunk(element); // } - // if (xQueueSend(pcmChkQHdl, &pcmChunk, pdMS_TO_TICKS(10)) != pdTRUE) { - if (xQueueSend(pcmChkQHdl, &pcmChunk, pdMS_TO_TICKS(1)) != pdTRUE) { + if (xQueueSend(pcmChkQHdl, &pcmChunk, pdMS_TO_TICKS(CONFIG_PLAYER_QUEUE_INSERT_TIMEOUT_MS)) != pdTRUE) { ESP_LOGV(TAG, "send: pcmChunkQueue full, messages waiting %d", uxQueueMessagesWaiting(pcmChkQHdl)); @@ -1279,20 +1278,7 @@ static void player_task(void *pvParameters) { adjust_apll(0); #endif -// if (pcmChkQHdl == NULL) { -// int entries = ceil(((float)scSet.sr / (float)scSet.chkInFrames) * -// ((float)scSet.buf_ms / 1000)); -// -// // some chunks are placed in DMA buffer -// // so we can save a little RAM here -// entries -= (i2sDmaBufMaxLen * i2sDmaBufCnt) / scSet.chkInFrames; -// -// queueCreatedWithChkInFrames = scSet.chkInFrames; -// -// pcmChkQHdl = xQueueCreate(entries, sizeof(pcm_chunk_message_t *)); -// -// ESP_LOGI(TAG, "created new queue with %d", entries); -// } + // Queue is now created in start_player() for proper initialization audio_set_mute(scSet.muted); // wait for early time syncs to be ready @@ -1367,7 +1353,7 @@ static void player_task(void *pvParameters) { pcmChkQHdl = xQueueCreate(entries, sizeof(pcm_chunk_message_t *)); - ESP_LOGI(TAG, "created new queue with %d", entries); + ESP_LOGI(TAG, "created new queue with %d entries", entries); } if ((scSet.sr != __scSet.sr) || (scSet.bits != __scSet.bits) || @@ -1800,9 +1786,19 @@ static void player_task(void *pvParameters) { int msgWaiting = uxQueueMessagesWaiting(pcmChkQHdl); + // Track consecutive empty queue reads for hysteresis + static int consecutive_empty_count = 0; + + if (msgWaiting == 0) { + consecutive_empty_count++; + } else { + consecutive_empty_count = 0; + } + // resync hard if we are getting very late / early. // rest gets tuned in through apll speed control or sample insertion - if ((msgWaiting == 0) || + // Use hysteresis for queue empty to tolerate brief WiFi dropouts + if ((consecutive_empty_count >= CONFIG_PLAYER_QUEUE_EMPTY_THRESHOLD) || (MEDIANFILTER_isFull(&shortMedianFilter, 0) && ((shortMedian > hardResyncThreshold) || (shortMedian < -hardResyncThreshold)))) diff --git a/components/settings_manager/include/settings_manager.h b/components/settings_manager/include/settings_manager.h index cb78d0ed..955ce5d8 100644 --- a/components/settings_manager/include/settings_manager.h +++ b/components/settings_manager/include/settings_manager.h @@ -41,6 +41,43 @@ esp_err_t settings_get_server_port(int32_t *port); esp_err_t settings_set_server_port(int32_t port); esp_err_t settings_clear_server_port(void); +/* WiFi Resilience Settings */ + +/* TCP No Delay - disable Nagle's algorithm for lower latency */ +esp_err_t settings_get_tcp_nodelay(bool *enabled); +esp_err_t settings_set_tcp_nodelay(bool enabled); +esp_err_t settings_clear_tcp_nodelay(void); + +/* Queue Empty Threshold - consecutive empties before hard resync */ +esp_err_t settings_get_queue_empty_threshold(int32_t *value); +esp_err_t settings_set_queue_empty_threshold(int32_t value); +esp_err_t settings_clear_queue_empty_threshold(void); + +/* Queue Insert Timeout - ms to wait for queue space */ +esp_err_t settings_get_queue_insert_timeout(int32_t *value); +esp_err_t settings_set_queue_insert_timeout(int32_t value); +esp_err_t settings_clear_queue_insert_timeout(void); + +/* Fast Sync Latency - tolerance in microseconds */ +esp_err_t settings_get_fast_sync_latency(int32_t *value); +esp_err_t settings_set_fast_sync_latency(int32_t value); +esp_err_t settings_clear_fast_sync_latency(void); + +/* Reconnect Min Delay - initial delay in ms */ +esp_err_t settings_get_reconnect_min_delay(int32_t *value); +esp_err_t settings_set_reconnect_min_delay(int32_t value); +esp_err_t settings_clear_reconnect_min_delay(void); + +/* Reconnect Max Delay - max backoff delay in ms */ +esp_err_t settings_get_reconnect_max_delay(int32_t *value); +esp_err_t settings_set_reconnect_max_delay(int32_t value); +esp_err_t settings_clear_reconnect_max_delay(void); + +/* Buffer Headroom - extra buffer capacity as percentage */ +esp_err_t settings_get_buffer_headroom(int32_t *value); +esp_err_t settings_set_buffer_headroom(int32_t value); +esp_err_t settings_clear_buffer_headroom(void); + /** * Get all settings as a JSON string * @param json_out Buffer to store JSON string (caller must allocate) diff --git a/components/settings_manager/settings_manager.c b/components/settings_manager/settings_manager.c index 31e97137..af02f957 100644 --- a/components/settings_manager/settings_manager.c +++ b/components/settings_manager/settings_manager.c @@ -22,6 +22,15 @@ static const char *NVS_KEY_MDNS = "mdns"; // int32 0/1 static const char *NVS_KEY_SERVER_HOST = "server_host"; // string static const char *NVS_KEY_SERVER_PORT = "server_port"; // int32 +// WiFi Resilience settings +static const char *NVS_KEY_TCP_NODELAY = "tcp_nodelay"; // int32 0/1 +static const char *NVS_KEY_QUEUE_EMPTY_THRESH = "q_empty_thr"; // int32 +static const char *NVS_KEY_QUEUE_INSERT_TO = "q_insert_to"; // int32 +static const char *NVS_KEY_FAST_SYNC_LAT = "fast_sync_lat"; // int32 +static const char *NVS_KEY_RECONNECT_MIN = "reconn_min"; // int32 +static const char *NVS_KEY_RECONNECT_MAX = "reconn_max"; // int32 +static const char *NVS_KEY_BUFFER_HEADROOM = "buf_headroom"; // int32 + // Mutex for thread-safe NVS access static SemaphoreHandle_t hostname_mutex = NULL; @@ -590,3 +599,463 @@ esp_err_t settings_set_from_json(const char *json_in) { cJSON_Delete(root); return err; } + +/* ========== WiFi Resilience Settings ========== */ + +/* TCP No Delay */ +esp_err_t settings_get_tcp_nodelay(bool *enabled) { + if (!enabled) return ESP_ERR_INVALID_ARG; + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); + if (err == ESP_OK) { + int32_t v = 0; + err = nvs_get_i32(h, NVS_KEY_TCP_NODELAY, &v); + nvs_close(h); + if (err == ESP_OK) { + *enabled = (v != 0); + xSemaphoreGive(hostname_mutex); + return ESP_OK; + } + } + // Default from Kconfig +#ifdef CONFIG_WIFI_TCP_NODELAY + *enabled = true; +#else + *enabled = true; // Default to enabled +#endif + xSemaphoreGive(hostname_mutex); + return ESP_OK; +} + +esp_err_t settings_set_tcp_nodelay(bool enabled) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return err; + } + + err = nvs_set_i32(h, NVS_KEY_TCP_NODELAY, enabled ? 1 : 0); + if (err == ESP_OK) err = nvs_commit(h); + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +esp_err_t settings_clear_tcp_nodelay(void) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; + } + + err = nvs_erase_key(h, NVS_KEY_TCP_NODELAY); + if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { + nvs_commit(h); + err = ESP_OK; + } + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +/* Queue Empty Threshold */ +esp_err_t settings_get_queue_empty_threshold(int32_t *value) { + if (!value) return ESP_ERR_INVALID_ARG; + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); + if (err == ESP_OK) { + err = nvs_get_i32(h, NVS_KEY_QUEUE_EMPTY_THRESH, value); + nvs_close(h); + if (err == ESP_OK) { + xSemaphoreGive(hostname_mutex); + return ESP_OK; + } + } +#ifdef CONFIG_PLAYER_QUEUE_EMPTY_THRESHOLD + *value = CONFIG_PLAYER_QUEUE_EMPTY_THRESHOLD; +#else + *value = 3; +#endif + xSemaphoreGive(hostname_mutex); + return ESP_OK; +} + +esp_err_t settings_set_queue_empty_threshold(int32_t value) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return err; + } + + err = nvs_set_i32(h, NVS_KEY_QUEUE_EMPTY_THRESH, value); + if (err == ESP_OK) err = nvs_commit(h); + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +esp_err_t settings_clear_queue_empty_threshold(void) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; + } + + err = nvs_erase_key(h, NVS_KEY_QUEUE_EMPTY_THRESH); + if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { + nvs_commit(h); + err = ESP_OK; + } + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +/* Queue Insert Timeout */ +esp_err_t settings_get_queue_insert_timeout(int32_t *value) { + if (!value) return ESP_ERR_INVALID_ARG; + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); + if (err == ESP_OK) { + err = nvs_get_i32(h, NVS_KEY_QUEUE_INSERT_TO, value); + nvs_close(h); + if (err == ESP_OK) { + xSemaphoreGive(hostname_mutex); + return ESP_OK; + } + } +#ifdef CONFIG_PLAYER_QUEUE_INSERT_TIMEOUT_MS + *value = CONFIG_PLAYER_QUEUE_INSERT_TIMEOUT_MS; +#else + *value = 50; +#endif + xSemaphoreGive(hostname_mutex); + return ESP_OK; +} + +esp_err_t settings_set_queue_insert_timeout(int32_t value) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return err; + } + + err = nvs_set_i32(h, NVS_KEY_QUEUE_INSERT_TO, value); + if (err == ESP_OK) err = nvs_commit(h); + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +esp_err_t settings_clear_queue_insert_timeout(void) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; + } + + err = nvs_erase_key(h, NVS_KEY_QUEUE_INSERT_TO); + if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { + nvs_commit(h); + err = ESP_OK; + } + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +/* Fast Sync Latency */ +esp_err_t settings_get_fast_sync_latency(int32_t *value) { + if (!value) return ESP_ERR_INVALID_ARG; + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); + if (err == ESP_OK) { + err = nvs_get_i32(h, NVS_KEY_FAST_SYNC_LAT, value); + nvs_close(h); + if (err == ESP_OK) { + xSemaphoreGive(hostname_mutex); + return ESP_OK; + } + } +#ifdef CONFIG_WIFI_FAST_SYNC_LATENCY_US + *value = CONFIG_WIFI_FAST_SYNC_LATENCY_US; +#else + *value = 50000; +#endif + xSemaphoreGive(hostname_mutex); + return ESP_OK; +} + +esp_err_t settings_set_fast_sync_latency(int32_t value) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return err; + } + + err = nvs_set_i32(h, NVS_KEY_FAST_SYNC_LAT, value); + if (err == ESP_OK) err = nvs_commit(h); + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +esp_err_t settings_clear_fast_sync_latency(void) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; + } + + err = nvs_erase_key(h, NVS_KEY_FAST_SYNC_LAT); + if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { + nvs_commit(h); + err = ESP_OK; + } + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +/* Reconnect Min Delay */ +esp_err_t settings_get_reconnect_min_delay(int32_t *value) { + if (!value) return ESP_ERR_INVALID_ARG; + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); + if (err == ESP_OK) { + err = nvs_get_i32(h, NVS_KEY_RECONNECT_MIN, value); + nvs_close(h); + if (err == ESP_OK) { + xSemaphoreGive(hostname_mutex); + return ESP_OK; + } + } +#ifdef CONFIG_WIFI_RECONNECT_MIN_DELAY_MS + *value = CONFIG_WIFI_RECONNECT_MIN_DELAY_MS; +#else + *value = 1000; +#endif + xSemaphoreGive(hostname_mutex); + return ESP_OK; +} + +esp_err_t settings_set_reconnect_min_delay(int32_t value) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return err; + } + + err = nvs_set_i32(h, NVS_KEY_RECONNECT_MIN, value); + if (err == ESP_OK) err = nvs_commit(h); + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +esp_err_t settings_clear_reconnect_min_delay(void) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; + } + + err = nvs_erase_key(h, NVS_KEY_RECONNECT_MIN); + if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { + nvs_commit(h); + err = ESP_OK; + } + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +/* Reconnect Max Delay */ +esp_err_t settings_get_reconnect_max_delay(int32_t *value) { + if (!value) return ESP_ERR_INVALID_ARG; + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); + if (err == ESP_OK) { + err = nvs_get_i32(h, NVS_KEY_RECONNECT_MAX, value); + nvs_close(h); + if (err == ESP_OK) { + xSemaphoreGive(hostname_mutex); + return ESP_OK; + } + } +#ifdef CONFIG_WIFI_RECONNECT_MAX_DELAY_MS + *value = CONFIG_WIFI_RECONNECT_MAX_DELAY_MS; +#else + *value = 30000; +#endif + xSemaphoreGive(hostname_mutex); + return ESP_OK; +} + +esp_err_t settings_set_reconnect_max_delay(int32_t value) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return err; + } + + err = nvs_set_i32(h, NVS_KEY_RECONNECT_MAX, value); + if (err == ESP_OK) err = nvs_commit(h); + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +esp_err_t settings_clear_reconnect_max_delay(void) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; + } + + err = nvs_erase_key(h, NVS_KEY_RECONNECT_MAX); + if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { + nvs_commit(h); + err = ESP_OK; + } + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +/* Buffer Headroom */ +esp_err_t settings_get_buffer_headroom(int32_t *value) { + if (!value) return ESP_ERR_INVALID_ARG; + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); + if (err == ESP_OK) { + err = nvs_get_i32(h, NVS_KEY_BUFFER_HEADROOM, value); + nvs_close(h); + if (err == ESP_OK) { + xSemaphoreGive(hostname_mutex); + return ESP_OK; + } + } +#ifdef CONFIG_PLAYER_BUFFER_HEADROOM_PCT + *value = CONFIG_PLAYER_BUFFER_HEADROOM_PCT; +#else + *value = 50; +#endif + xSemaphoreGive(hostname_mutex); + return ESP_OK; +} + +esp_err_t settings_set_buffer_headroom(int32_t value) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return err; + } + + err = nvs_set_i32(h, NVS_KEY_BUFFER_HEADROOM, value); + if (err == ESP_OK) err = nvs_commit(h); + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} + +esp_err_t settings_clear_buffer_headroom(void) { + if (!hostname_mutex) return ESP_ERR_INVALID_STATE; + if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; + + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); + if (err != ESP_OK) { + xSemaphoreGive(hostname_mutex); + return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; + } + + err = nvs_erase_key(h, NVS_KEY_BUFFER_HEADROOM); + if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { + nvs_commit(h); + err = ESP_OK; + } + nvs_close(h); + xSemaphoreGive(hostname_mutex); + return err; +} diff --git a/components/ui_http_server/CMakeLists.txt b/components/ui_http_server/CMakeLists.txt index d4d73092..409a100b 100644 --- a/components/ui_http_server/CMakeLists.txt +++ b/components/ui_http_server/CMakeLists.txt @@ -6,4 +6,5 @@ idf_component_register(SRCS "ui_http_server.c" html/styles.css html/general-settings.html html/dsp-settings.html + html/advanced-settings.html EMBED_FILES html/favicon.ico) diff --git a/components/ui_http_server/html/advanced-settings.html b/components/ui_http_server/html/advanced-settings.html new file mode 100644 index 00000000..7c63e417 --- /dev/null +++ b/components/ui_http_server/html/advanced-settings.html @@ -0,0 +1,367 @@ + + + + + + Advanced Settings + + + + + +

Advanced Settings

+ +
+
Loading settings...
+
+ + + + diff --git a/components/ui_http_server/html/index.html b/components/ui_http_server/html/index.html index af39ec99..66706f1b 100644 --- a/components/ui_http_server/html/index.html +++ b/components/ui_http_server/html/index.html @@ -90,6 +90,7 @@

ESP32 Snapclient

diff --git a/components/ui_http_server/ui_http_server.c b/components/ui_http_server/ui_http_server.c index 06f02efa..b5d86ed8 100644 --- a/components/ui_http_server/ui_http_server.c +++ b/components/ui_http_server/ui_http_server.c @@ -43,6 +43,8 @@ extern const uint8_t dsp_settings_html_start[] asm("_binary_dsp_settings_html_st extern const uint8_t dsp_settings_html_end[] asm("_binary_dsp_settings_html_end"); extern const uint8_t favicon_ico_start[] asm("_binary_favicon_ico_start"); extern const uint8_t favicon_ico_end[] asm("_binary_favicon_ico_end"); +extern const uint8_t advanced_settings_html_start[] asm("_binary_advanced_settings_html_start"); +extern const uint8_t advanced_settings_html_end[] asm("_binary_advanced_settings_html_end"); // Structure to map URI paths to embedded files typedef struct { @@ -59,6 +61,7 @@ static const embedded_file_t embedded_files[] = { {"/styles.css", styles_css_start, styles_css_end, "text/css; charset=utf-8"}, {"/general-settings.html", general_settings_html_start, general_settings_html_end, "text/html; charset=utf-8"}, {"/dsp-settings.html", dsp_settings_html_start, dsp_settings_html_end, "text/html; charset=utf-8"}, + {"/advanced-settings.html", advanced_settings_html_start, advanced_settings_html_end, "text/html; charset=utf-8"}, {"/favicon.ico", favicon_ico_start, favicon_ico_end, "image/x-icon"}, }; @@ -229,6 +232,98 @@ static esp_err_t root_post_handler(httpd_req_t *req) { return ESP_OK; } + // WiFi Resilience Settings + if (strcmp(param, "tcp_nodelay") == 0) { + bool v = (strtol(valstr, NULL, 10) != 0); + ESP_LOGI(TAG, "%s: Setting tcp_nodelay to: %d", __func__, v ? 1 : 0); + if (settings_set_tcp_nodelay(v) == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "500 Internal Server Error"); + httpd_resp_sendstr(req, "error"); + } + return ESP_OK; + } + + if (strcmp(param, "queue_empty_threshold") == 0) { + long v = strtol(valstr, NULL, 10); + ESP_LOGI(TAG, "%s: Setting queue_empty_threshold to: %ld", __func__, v); + if (v >= 1 && v <= 10 && settings_set_queue_empty_threshold((int32_t)v) == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_sendstr(req, "Invalid value (range: 1-10)"); + } + return ESP_OK; + } + + if (strcmp(param, "queue_insert_timeout") == 0) { + long v = strtol(valstr, NULL, 10); + ESP_LOGI(TAG, "%s: Setting queue_insert_timeout to: %ld", __func__, v); + if (v >= 1 && v <= 200 && settings_set_queue_insert_timeout((int32_t)v) == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_sendstr(req, "Invalid value (range: 1-200)"); + } + return ESP_OK; + } + + if (strcmp(param, "fast_sync_latency") == 0) { + long v = strtol(valstr, NULL, 10); + ESP_LOGI(TAG, "%s: Setting fast_sync_latency to: %ld", __func__, v); + if (v >= 10000 && v <= 100000 && settings_set_fast_sync_latency((int32_t)v) == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_sendstr(req, "Invalid value (range: 10000-100000)"); + } + return ESP_OK; + } + + if (strcmp(param, "reconnect_min_delay") == 0) { + long v = strtol(valstr, NULL, 10); + ESP_LOGI(TAG, "%s: Setting reconnect_min_delay to: %ld", __func__, v); + if (v >= 100 && v <= 5000 && settings_set_reconnect_min_delay((int32_t)v) == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_sendstr(req, "Invalid value (range: 100-5000)"); + } + return ESP_OK; + } + + if (strcmp(param, "reconnect_max_delay") == 0) { + long v = strtol(valstr, NULL, 10); + ESP_LOGI(TAG, "%s: Setting reconnect_max_delay to: %ld", __func__, v); + if (v >= 5000 && v <= 60000 && settings_set_reconnect_max_delay((int32_t)v) == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_sendstr(req, "Invalid value (range: 5000-60000)"); + } + return ESP_OK; + } + + if (strcmp(param, "buffer_headroom") == 0) { + long v = strtol(valstr, NULL, 10); + ESP_LOGI(TAG, "%s: Setting buffer_headroom to: %ld", __func__, v); + if (v >= 0 && v <= 100 && settings_set_buffer_headroom((int32_t)v) == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_sendstr(req, "Invalid value (range: 0-100)"); + } + return ESP_OK; + } + // Parse integer value; strtol skips leading whitespace long v = strtol(valstr, NULL, 10); urlBuf.int_value = (int32_t)v; @@ -328,6 +423,91 @@ static esp_err_t root_delete_handler(httpd_req_t *req) { return ESP_OK; } + // WiFi Resilience Settings - clear handlers + if (strcmp(param, "tcp_nodelay") == 0) { + ESP_LOGI(TAG, "%s: Clearing tcp_nodelay from NVS", __func__); + if (settings_clear_tcp_nodelay() == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "500 Internal Server Error"); + httpd_resp_sendstr(req, "error"); + } + return ESP_OK; + } + + if (strcmp(param, "queue_empty_threshold") == 0) { + ESP_LOGI(TAG, "%s: Clearing queue_empty_threshold from NVS", __func__); + if (settings_clear_queue_empty_threshold() == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "500 Internal Server Error"); + httpd_resp_sendstr(req, "error"); + } + return ESP_OK; + } + + if (strcmp(param, "queue_insert_timeout") == 0) { + ESP_LOGI(TAG, "%s: Clearing queue_insert_timeout from NVS", __func__); + if (settings_clear_queue_insert_timeout() == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "500 Internal Server Error"); + httpd_resp_sendstr(req, "error"); + } + return ESP_OK; + } + + if (strcmp(param, "fast_sync_latency") == 0) { + ESP_LOGI(TAG, "%s: Clearing fast_sync_latency from NVS", __func__); + if (settings_clear_fast_sync_latency() == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "500 Internal Server Error"); + httpd_resp_sendstr(req, "error"); + } + return ESP_OK; + } + + if (strcmp(param, "reconnect_min_delay") == 0) { + ESP_LOGI(TAG, "%s: Clearing reconnect_min_delay from NVS", __func__); + if (settings_clear_reconnect_min_delay() == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "500 Internal Server Error"); + httpd_resp_sendstr(req, "error"); + } + return ESP_OK; + } + + if (strcmp(param, "reconnect_max_delay") == 0) { + ESP_LOGI(TAG, "%s: Clearing reconnect_max_delay from NVS", __func__); + if (settings_clear_reconnect_max_delay() == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "500 Internal Server Error"); + httpd_resp_sendstr(req, "error"); + } + return ESP_OK; + } + + if (strcmp(param, "buffer_headroom") == 0) { + ESP_LOGI(TAG, "%s: Clearing buffer_headroom from NVS", __func__); + if (settings_clear_buffer_headroom() == ESP_OK) { + httpd_resp_set_status(req, "200 OK"); + httpd_resp_sendstr(req, "ok"); + } else { + httpd_resp_set_status(req, "500 Internal Server Error"); + httpd_resp_sendstr(req, "error"); + } + return ESP_OK; + } + // Unknown parameter httpd_resp_set_status(req, "400 Bad Request"); httpd_resp_sendstr(req, "Unknown parameter"); @@ -568,12 +748,61 @@ static esp_err_t get_capabilities_handler(httpd_req_t *req) { httpd_resp_sendstr(req, "{\"dsp_enabled\": false}"); #endif + } else if (strcmp(tab, "advanced") == 0) { + // Return WiFi Resilience / Advanced settings + char json_buf[512]; + int len = 0; + + // Get all WiFi resilience settings + bool tcp_nodelay = true; + int32_t queue_empty_threshold = 3; + int32_t queue_insert_timeout = 50; + int32_t fast_sync_latency = 50000; + int32_t reconnect_min_delay = 1000; + int32_t reconnect_max_delay = 30000; + int32_t buffer_headroom = 50; + + settings_get_tcp_nodelay(&tcp_nodelay); + settings_get_queue_empty_threshold(&queue_empty_threshold); + settings_get_queue_insert_timeout(&queue_insert_timeout); + settings_get_fast_sync_latency(&fast_sync_latency); + settings_get_reconnect_min_delay(&reconnect_min_delay); + settings_get_reconnect_max_delay(&reconnect_max_delay); + settings_get_buffer_headroom(&buffer_headroom); + + len = snprintf(json_buf, sizeof(json_buf), + "{\"tcp_nodelay\":%s," + "\"queue_empty_threshold\":%d," + "\"queue_insert_timeout\":%d," + "\"fast_sync_latency\":%d," + "\"reconnect_min_delay\":%d," + "\"reconnect_max_delay\":%d," + "\"buffer_headroom\":%d}", + tcp_nodelay ? "true" : "false", + (int)queue_empty_threshold, + (int)queue_insert_timeout, + (int)fast_sync_latency, + (int)reconnect_min_delay, + (int)reconnect_max_delay, + (int)buffer_headroom); + + if (len >= (int)sizeof(json_buf)) { + ESP_LOGE(TAG, "%s: JSON buffer overflow", __func__); + httpd_resp_set_status(req, "500 Internal Server Error"); + httpd_resp_sendstr(req, "{\"error\": \"Buffer overflow\"}"); + return ESP_OK; + } + + httpd_resp_set_status(req, "200 OK"); + httpd_resp_set_type(req, "application/json"); + httpd_resp_sendstr(req, json_buf); + } else { // Unknown tab ESP_LOGW(TAG, "%s: Unknown tab: %s", __func__, tab); httpd_resp_set_status(req, "400 Bad Request"); httpd_resp_sendstr( - req, "{\"error\": \"Unknown tab. Use ?tab=general or ?tab=dsp\"}"); + req, "{\"error\": \"Unknown tab. Use ?tab=general, ?tab=dsp, or ?tab=advanced\"}"); } return ESP_OK; diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 1aa0ac0a..b006318b 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -51,3 +51,37 @@ menu "Snapclient Configuration" Both approaches have similar performance keeping clients in sync <= 500µs endmenu + +menu "WiFi Resilience Settings" + config WIFI_TCP_NODELAY + bool "Enable TCP_NODELAY" + default y + help + Disable Nagle's algorithm for lower latency on TCP connections. + Recommended for real-time audio streaming. + + config WIFI_FAST_SYNC_LATENCY_US + int "Fast sync latency buffer (µs)" + range 10000 100000 + default 50000 + help + Time tolerance for fast sync operations in microseconds. + Higher values are more tolerant of WiFi latency jitter. + Default: 50000 (50ms) + + config WIFI_RECONNECT_MIN_DELAY_MS + int "Reconnect minimum delay (ms)" + range 100 5000 + default 1000 + help + Initial delay before reconnection attempt. + Default: 1000 (1 second) + + config WIFI_RECONNECT_MAX_DELAY_MS + int "Reconnect maximum delay (ms)" + range 5000 60000 + default 30000 + help + Maximum delay with exponential backoff. + Default: 30000 (30 seconds) +endmenu diff --git a/main/main.c b/main/main.c index 439a0eb0..09d9d8e0 100644 --- a/main/main.c +++ b/main/main.c @@ -32,6 +32,7 @@ #include "lwip/sockets.h" #include "lwip/sys.h" #include "lwip/tcp.h" +#include "esp_random.h" #include "mdns.h" #include "net_functions.h" #include "network_interface.h" @@ -461,7 +462,8 @@ static void http_get_task(void *pvParameters) { int rc1 = ERR_OK, rc2 = ERR_OK; struct netbuf *firstNetBuf = NULL; uint16_t len; - uint64_t timeout; + uint64_t timeout = FAST_SYNC_LATENCY_BUF; + static uint32_t reconnect_attempts = 0; // For exponential backoff char *codecString = NULL; char *codecPayload = NULL; char *serverSettingsString = NULL; @@ -688,9 +690,18 @@ static void http_get_task(void *pvParameters) { continue; } - -// netconn_set_flags(lwipNetconn, TF_NODELAY); - + +#if CONFIG_WIFI_TCP_NODELAY + // Disable Nagle's algorithm for lower latency + { + struct tcp_pcb *pcb = (struct tcp_pcb *)lwipNetconn->pcb.tcp; + if (pcb != NULL) { + tcp_nagle_disable(pcb); + ESP_LOGI(TAG, "TCP_NODELAY enabled for lower latency"); + } + } +#endif + #define USE_INTERFACE_BIND #ifdef USE_INTERFACE_BIND // use interface to bind connection @@ -717,10 +728,6 @@ static void http_get_task(void *pvParameters) { if (rc2 != ERR_OK) { ESP_LOGE(TAG, "can't connect to remote %s:%d, err %d", ipaddr_ntoa(&remote_ip), remotePort, rc2); - -#if !SNAPCAST_SERVER_USE_MDNS - vTaskDelay(pdMS_TO_TICKS(1000)); -#endif } if (rc1 != ERR_OK || rc2 != ERR_OK) { @@ -728,9 +735,26 @@ static void http_get_task(void *pvParameters) { netconn_delete(lwipNetconn); lwipNetconn = NULL; + // Exponential backoff with jitter for reconnection + uint32_t delay_ms = (uint32_t)CONFIG_WIFI_RECONNECT_MIN_DELAY_MS << reconnect_attempts; + if (delay_ms > (uint32_t)CONFIG_WIFI_RECONNECT_MAX_DELAY_MS) { + delay_ms = (uint32_t)CONFIG_WIFI_RECONNECT_MAX_DELAY_MS; + } + delay_ms += (esp_random() % 500); // Add jitter + + ESP_LOGW(TAG, "Reconnect attempt %lu, waiting %lums", + (unsigned long)reconnect_attempts, (unsigned long)delay_ms); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + + if (reconnect_attempts < 10) { // Cap attempts counter + reconnect_attempts++; + } + continue; } + // Reset backoff on successful connection + reconnect_attempts = 0; ESP_LOGI(TAG, "netconn connected using %s", network_get_ifkey(netif)); //if (reset_latency_buffer() < 0) { From 03d967bbd91cdcd4b67c891a7cdf4974f9a4abc6 Mon Sep 17 00:00:00 2001 From: craigmillard86 Date: Sun, 18 Jan 2026 18:04:14 +0000 Subject: [PATCH 2/2] refactor: Simplify WiFi resilience to Kconfig-only configuration Remove runtime UI and NVS settings for WiFi resilience in favor of compile-time Kconfig options. This keeps lightsnapcast as a clean library without settings_manager dependency. Changes: - Remove advanced-settings.html and UI handlers - Remove WiFi resilience functions from settings_manager - Use CONFIG_PLAYER_QUEUE_* directly in player.c - Use CONFIG_WIFI_* directly in main.c - Remove unused fast_sync_latency option Remaining Kconfig options: - WIFI_TCP_NODELAY, WIFI_RECONNECT_MIN/MAX_DELAY_MS (main) - PLAYER_QUEUE_EMPTY_THRESHOLD, PLAYER_QUEUE_INSERT_TIMEOUT_MS (lightsnapcast) --- components/lightsnapcast/Kconfig.projbuild | 10 - .../include/settings_manager.h | 37 -- .../settings_manager/settings_manager.c | 469 ------------------ components/ui_http_server/CMakeLists.txt | 1 - .../html/advanced-settings.html | 367 -------------- components/ui_http_server/html/index.html | 1 - components/ui_http_server/ui_http_server.c | 231 +-------- main/Kconfig.projbuild | 9 - 8 files changed, 1 insertion(+), 1124 deletions(-) delete mode 100644 components/ui_http_server/html/advanced-settings.html diff --git a/components/lightsnapcast/Kconfig.projbuild b/components/lightsnapcast/Kconfig.projbuild index 8d1ab6d3..a63f6a6e 100644 --- a/components/lightsnapcast/Kconfig.projbuild +++ b/components/lightsnapcast/Kconfig.projbuild @@ -16,14 +16,4 @@ menu "Lightsnapcast Player Settings" Timeout for inserting audio chunks into the playback queue. Higher values allow queue to drain during burst arrivals. Default: 50ms - - config PLAYER_BUFFER_HEADROOM_PCT - int "Buffer headroom percentage" - range 0 100 - default 50 if SPIRAM && SPIRAM_BOOT_INIT - default 20 - help - Extra buffer capacity as percentage of base requirement. - Provides headroom for WiFi jitter. Use higher values with PSRAM. - Default: 50% with PSRAM, 20% without endmenu diff --git a/components/settings_manager/include/settings_manager.h b/components/settings_manager/include/settings_manager.h index 955ce5d8..cb78d0ed 100644 --- a/components/settings_manager/include/settings_manager.h +++ b/components/settings_manager/include/settings_manager.h @@ -41,43 +41,6 @@ esp_err_t settings_get_server_port(int32_t *port); esp_err_t settings_set_server_port(int32_t port); esp_err_t settings_clear_server_port(void); -/* WiFi Resilience Settings */ - -/* TCP No Delay - disable Nagle's algorithm for lower latency */ -esp_err_t settings_get_tcp_nodelay(bool *enabled); -esp_err_t settings_set_tcp_nodelay(bool enabled); -esp_err_t settings_clear_tcp_nodelay(void); - -/* Queue Empty Threshold - consecutive empties before hard resync */ -esp_err_t settings_get_queue_empty_threshold(int32_t *value); -esp_err_t settings_set_queue_empty_threshold(int32_t value); -esp_err_t settings_clear_queue_empty_threshold(void); - -/* Queue Insert Timeout - ms to wait for queue space */ -esp_err_t settings_get_queue_insert_timeout(int32_t *value); -esp_err_t settings_set_queue_insert_timeout(int32_t value); -esp_err_t settings_clear_queue_insert_timeout(void); - -/* Fast Sync Latency - tolerance in microseconds */ -esp_err_t settings_get_fast_sync_latency(int32_t *value); -esp_err_t settings_set_fast_sync_latency(int32_t value); -esp_err_t settings_clear_fast_sync_latency(void); - -/* Reconnect Min Delay - initial delay in ms */ -esp_err_t settings_get_reconnect_min_delay(int32_t *value); -esp_err_t settings_set_reconnect_min_delay(int32_t value); -esp_err_t settings_clear_reconnect_min_delay(void); - -/* Reconnect Max Delay - max backoff delay in ms */ -esp_err_t settings_get_reconnect_max_delay(int32_t *value); -esp_err_t settings_set_reconnect_max_delay(int32_t value); -esp_err_t settings_clear_reconnect_max_delay(void); - -/* Buffer Headroom - extra buffer capacity as percentage */ -esp_err_t settings_get_buffer_headroom(int32_t *value); -esp_err_t settings_set_buffer_headroom(int32_t value); -esp_err_t settings_clear_buffer_headroom(void); - /** * Get all settings as a JSON string * @param json_out Buffer to store JSON string (caller must allocate) diff --git a/components/settings_manager/settings_manager.c b/components/settings_manager/settings_manager.c index af02f957..31e97137 100644 --- a/components/settings_manager/settings_manager.c +++ b/components/settings_manager/settings_manager.c @@ -22,15 +22,6 @@ static const char *NVS_KEY_MDNS = "mdns"; // int32 0/1 static const char *NVS_KEY_SERVER_HOST = "server_host"; // string static const char *NVS_KEY_SERVER_PORT = "server_port"; // int32 -// WiFi Resilience settings -static const char *NVS_KEY_TCP_NODELAY = "tcp_nodelay"; // int32 0/1 -static const char *NVS_KEY_QUEUE_EMPTY_THRESH = "q_empty_thr"; // int32 -static const char *NVS_KEY_QUEUE_INSERT_TO = "q_insert_to"; // int32 -static const char *NVS_KEY_FAST_SYNC_LAT = "fast_sync_lat"; // int32 -static const char *NVS_KEY_RECONNECT_MIN = "reconn_min"; // int32 -static const char *NVS_KEY_RECONNECT_MAX = "reconn_max"; // int32 -static const char *NVS_KEY_BUFFER_HEADROOM = "buf_headroom"; // int32 - // Mutex for thread-safe NVS access static SemaphoreHandle_t hostname_mutex = NULL; @@ -599,463 +590,3 @@ esp_err_t settings_set_from_json(const char *json_in) { cJSON_Delete(root); return err; } - -/* ========== WiFi Resilience Settings ========== */ - -/* TCP No Delay */ -esp_err_t settings_get_tcp_nodelay(bool *enabled) { - if (!enabled) return ESP_ERR_INVALID_ARG; - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); - if (err == ESP_OK) { - int32_t v = 0; - err = nvs_get_i32(h, NVS_KEY_TCP_NODELAY, &v); - nvs_close(h); - if (err == ESP_OK) { - *enabled = (v != 0); - xSemaphoreGive(hostname_mutex); - return ESP_OK; - } - } - // Default from Kconfig -#ifdef CONFIG_WIFI_TCP_NODELAY - *enabled = true; -#else - *enabled = true; // Default to enabled -#endif - xSemaphoreGive(hostname_mutex); - return ESP_OK; -} - -esp_err_t settings_set_tcp_nodelay(bool enabled) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return err; - } - - err = nvs_set_i32(h, NVS_KEY_TCP_NODELAY, enabled ? 1 : 0); - if (err == ESP_OK) err = nvs_commit(h); - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -esp_err_t settings_clear_tcp_nodelay(void) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; - } - - err = nvs_erase_key(h, NVS_KEY_TCP_NODELAY); - if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { - nvs_commit(h); - err = ESP_OK; - } - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -/* Queue Empty Threshold */ -esp_err_t settings_get_queue_empty_threshold(int32_t *value) { - if (!value) return ESP_ERR_INVALID_ARG; - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); - if (err == ESP_OK) { - err = nvs_get_i32(h, NVS_KEY_QUEUE_EMPTY_THRESH, value); - nvs_close(h); - if (err == ESP_OK) { - xSemaphoreGive(hostname_mutex); - return ESP_OK; - } - } -#ifdef CONFIG_PLAYER_QUEUE_EMPTY_THRESHOLD - *value = CONFIG_PLAYER_QUEUE_EMPTY_THRESHOLD; -#else - *value = 3; -#endif - xSemaphoreGive(hostname_mutex); - return ESP_OK; -} - -esp_err_t settings_set_queue_empty_threshold(int32_t value) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return err; - } - - err = nvs_set_i32(h, NVS_KEY_QUEUE_EMPTY_THRESH, value); - if (err == ESP_OK) err = nvs_commit(h); - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -esp_err_t settings_clear_queue_empty_threshold(void) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; - } - - err = nvs_erase_key(h, NVS_KEY_QUEUE_EMPTY_THRESH); - if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { - nvs_commit(h); - err = ESP_OK; - } - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -/* Queue Insert Timeout */ -esp_err_t settings_get_queue_insert_timeout(int32_t *value) { - if (!value) return ESP_ERR_INVALID_ARG; - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); - if (err == ESP_OK) { - err = nvs_get_i32(h, NVS_KEY_QUEUE_INSERT_TO, value); - nvs_close(h); - if (err == ESP_OK) { - xSemaphoreGive(hostname_mutex); - return ESP_OK; - } - } -#ifdef CONFIG_PLAYER_QUEUE_INSERT_TIMEOUT_MS - *value = CONFIG_PLAYER_QUEUE_INSERT_TIMEOUT_MS; -#else - *value = 50; -#endif - xSemaphoreGive(hostname_mutex); - return ESP_OK; -} - -esp_err_t settings_set_queue_insert_timeout(int32_t value) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return err; - } - - err = nvs_set_i32(h, NVS_KEY_QUEUE_INSERT_TO, value); - if (err == ESP_OK) err = nvs_commit(h); - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -esp_err_t settings_clear_queue_insert_timeout(void) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; - } - - err = nvs_erase_key(h, NVS_KEY_QUEUE_INSERT_TO); - if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { - nvs_commit(h); - err = ESP_OK; - } - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -/* Fast Sync Latency */ -esp_err_t settings_get_fast_sync_latency(int32_t *value) { - if (!value) return ESP_ERR_INVALID_ARG; - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); - if (err == ESP_OK) { - err = nvs_get_i32(h, NVS_KEY_FAST_SYNC_LAT, value); - nvs_close(h); - if (err == ESP_OK) { - xSemaphoreGive(hostname_mutex); - return ESP_OK; - } - } -#ifdef CONFIG_WIFI_FAST_SYNC_LATENCY_US - *value = CONFIG_WIFI_FAST_SYNC_LATENCY_US; -#else - *value = 50000; -#endif - xSemaphoreGive(hostname_mutex); - return ESP_OK; -} - -esp_err_t settings_set_fast_sync_latency(int32_t value) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return err; - } - - err = nvs_set_i32(h, NVS_KEY_FAST_SYNC_LAT, value); - if (err == ESP_OK) err = nvs_commit(h); - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -esp_err_t settings_clear_fast_sync_latency(void) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; - } - - err = nvs_erase_key(h, NVS_KEY_FAST_SYNC_LAT); - if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { - nvs_commit(h); - err = ESP_OK; - } - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -/* Reconnect Min Delay */ -esp_err_t settings_get_reconnect_min_delay(int32_t *value) { - if (!value) return ESP_ERR_INVALID_ARG; - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); - if (err == ESP_OK) { - err = nvs_get_i32(h, NVS_KEY_RECONNECT_MIN, value); - nvs_close(h); - if (err == ESP_OK) { - xSemaphoreGive(hostname_mutex); - return ESP_OK; - } - } -#ifdef CONFIG_WIFI_RECONNECT_MIN_DELAY_MS - *value = CONFIG_WIFI_RECONNECT_MIN_DELAY_MS; -#else - *value = 1000; -#endif - xSemaphoreGive(hostname_mutex); - return ESP_OK; -} - -esp_err_t settings_set_reconnect_min_delay(int32_t value) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return err; - } - - err = nvs_set_i32(h, NVS_KEY_RECONNECT_MIN, value); - if (err == ESP_OK) err = nvs_commit(h); - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -esp_err_t settings_clear_reconnect_min_delay(void) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; - } - - err = nvs_erase_key(h, NVS_KEY_RECONNECT_MIN); - if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { - nvs_commit(h); - err = ESP_OK; - } - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -/* Reconnect Max Delay */ -esp_err_t settings_get_reconnect_max_delay(int32_t *value) { - if (!value) return ESP_ERR_INVALID_ARG; - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); - if (err == ESP_OK) { - err = nvs_get_i32(h, NVS_KEY_RECONNECT_MAX, value); - nvs_close(h); - if (err == ESP_OK) { - xSemaphoreGive(hostname_mutex); - return ESP_OK; - } - } -#ifdef CONFIG_WIFI_RECONNECT_MAX_DELAY_MS - *value = CONFIG_WIFI_RECONNECT_MAX_DELAY_MS; -#else - *value = 30000; -#endif - xSemaphoreGive(hostname_mutex); - return ESP_OK; -} - -esp_err_t settings_set_reconnect_max_delay(int32_t value) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return err; - } - - err = nvs_set_i32(h, NVS_KEY_RECONNECT_MAX, value); - if (err == ESP_OK) err = nvs_commit(h); - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -esp_err_t settings_clear_reconnect_max_delay(void) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; - } - - err = nvs_erase_key(h, NVS_KEY_RECONNECT_MAX); - if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { - nvs_commit(h); - err = ESP_OK; - } - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -/* Buffer Headroom */ -esp_err_t settings_get_buffer_headroom(int32_t *value) { - if (!value) return ESP_ERR_INVALID_ARG; - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); - if (err == ESP_OK) { - err = nvs_get_i32(h, NVS_KEY_BUFFER_HEADROOM, value); - nvs_close(h); - if (err == ESP_OK) { - xSemaphoreGive(hostname_mutex); - return ESP_OK; - } - } -#ifdef CONFIG_PLAYER_BUFFER_HEADROOM_PCT - *value = CONFIG_PLAYER_BUFFER_HEADROOM_PCT; -#else - *value = 50; -#endif - xSemaphoreGive(hostname_mutex); - return ESP_OK; -} - -esp_err_t settings_set_buffer_headroom(int32_t value) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return err; - } - - err = nvs_set_i32(h, NVS_KEY_BUFFER_HEADROOM, value); - if (err == ESP_OK) err = nvs_commit(h); - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} - -esp_err_t settings_clear_buffer_headroom(void) { - if (!hostname_mutex) return ESP_ERR_INVALID_STATE; - if (xSemaphoreTake(hostname_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) return ESP_ERR_TIMEOUT; - - nvs_handle_t h; - esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); - if (err != ESP_OK) { - xSemaphoreGive(hostname_mutex); - return (err == ESP_ERR_NVS_NOT_FOUND) ? ESP_OK : err; - } - - err = nvs_erase_key(h, NVS_KEY_BUFFER_HEADROOM); - if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) { - nvs_commit(h); - err = ESP_OK; - } - nvs_close(h); - xSemaphoreGive(hostname_mutex); - return err; -} diff --git a/components/ui_http_server/CMakeLists.txt b/components/ui_http_server/CMakeLists.txt index 409a100b..d4d73092 100644 --- a/components/ui_http_server/CMakeLists.txt +++ b/components/ui_http_server/CMakeLists.txt @@ -6,5 +6,4 @@ idf_component_register(SRCS "ui_http_server.c" html/styles.css html/general-settings.html html/dsp-settings.html - html/advanced-settings.html EMBED_FILES html/favicon.ico) diff --git a/components/ui_http_server/html/advanced-settings.html b/components/ui_http_server/html/advanced-settings.html deleted file mode 100644 index 7c63e417..00000000 --- a/components/ui_http_server/html/advanced-settings.html +++ /dev/null @@ -1,367 +0,0 @@ - - - - - - Advanced Settings - - - - - -

Advanced Settings

- -
-
Loading settings...
-
- - - - diff --git a/components/ui_http_server/html/index.html b/components/ui_http_server/html/index.html index 66706f1b..af39ec99 100644 --- a/components/ui_http_server/html/index.html +++ b/components/ui_http_server/html/index.html @@ -90,7 +90,6 @@

ESP32 Snapclient

diff --git a/components/ui_http_server/ui_http_server.c b/components/ui_http_server/ui_http_server.c index b5d86ed8..06f02efa 100644 --- a/components/ui_http_server/ui_http_server.c +++ b/components/ui_http_server/ui_http_server.c @@ -43,8 +43,6 @@ extern const uint8_t dsp_settings_html_start[] asm("_binary_dsp_settings_html_st extern const uint8_t dsp_settings_html_end[] asm("_binary_dsp_settings_html_end"); extern const uint8_t favicon_ico_start[] asm("_binary_favicon_ico_start"); extern const uint8_t favicon_ico_end[] asm("_binary_favicon_ico_end"); -extern const uint8_t advanced_settings_html_start[] asm("_binary_advanced_settings_html_start"); -extern const uint8_t advanced_settings_html_end[] asm("_binary_advanced_settings_html_end"); // Structure to map URI paths to embedded files typedef struct { @@ -61,7 +59,6 @@ static const embedded_file_t embedded_files[] = { {"/styles.css", styles_css_start, styles_css_end, "text/css; charset=utf-8"}, {"/general-settings.html", general_settings_html_start, general_settings_html_end, "text/html; charset=utf-8"}, {"/dsp-settings.html", dsp_settings_html_start, dsp_settings_html_end, "text/html; charset=utf-8"}, - {"/advanced-settings.html", advanced_settings_html_start, advanced_settings_html_end, "text/html; charset=utf-8"}, {"/favicon.ico", favicon_ico_start, favicon_ico_end, "image/x-icon"}, }; @@ -232,98 +229,6 @@ static esp_err_t root_post_handler(httpd_req_t *req) { return ESP_OK; } - // WiFi Resilience Settings - if (strcmp(param, "tcp_nodelay") == 0) { - bool v = (strtol(valstr, NULL, 10) != 0); - ESP_LOGI(TAG, "%s: Setting tcp_nodelay to: %d", __func__, v ? 1 : 0); - if (settings_set_tcp_nodelay(v) == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "500 Internal Server Error"); - httpd_resp_sendstr(req, "error"); - } - return ESP_OK; - } - - if (strcmp(param, "queue_empty_threshold") == 0) { - long v = strtol(valstr, NULL, 10); - ESP_LOGI(TAG, "%s: Setting queue_empty_threshold to: %ld", __func__, v); - if (v >= 1 && v <= 10 && settings_set_queue_empty_threshold((int32_t)v) == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "400 Bad Request"); - httpd_resp_sendstr(req, "Invalid value (range: 1-10)"); - } - return ESP_OK; - } - - if (strcmp(param, "queue_insert_timeout") == 0) { - long v = strtol(valstr, NULL, 10); - ESP_LOGI(TAG, "%s: Setting queue_insert_timeout to: %ld", __func__, v); - if (v >= 1 && v <= 200 && settings_set_queue_insert_timeout((int32_t)v) == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "400 Bad Request"); - httpd_resp_sendstr(req, "Invalid value (range: 1-200)"); - } - return ESP_OK; - } - - if (strcmp(param, "fast_sync_latency") == 0) { - long v = strtol(valstr, NULL, 10); - ESP_LOGI(TAG, "%s: Setting fast_sync_latency to: %ld", __func__, v); - if (v >= 10000 && v <= 100000 && settings_set_fast_sync_latency((int32_t)v) == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "400 Bad Request"); - httpd_resp_sendstr(req, "Invalid value (range: 10000-100000)"); - } - return ESP_OK; - } - - if (strcmp(param, "reconnect_min_delay") == 0) { - long v = strtol(valstr, NULL, 10); - ESP_LOGI(TAG, "%s: Setting reconnect_min_delay to: %ld", __func__, v); - if (v >= 100 && v <= 5000 && settings_set_reconnect_min_delay((int32_t)v) == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "400 Bad Request"); - httpd_resp_sendstr(req, "Invalid value (range: 100-5000)"); - } - return ESP_OK; - } - - if (strcmp(param, "reconnect_max_delay") == 0) { - long v = strtol(valstr, NULL, 10); - ESP_LOGI(TAG, "%s: Setting reconnect_max_delay to: %ld", __func__, v); - if (v >= 5000 && v <= 60000 && settings_set_reconnect_max_delay((int32_t)v) == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "400 Bad Request"); - httpd_resp_sendstr(req, "Invalid value (range: 5000-60000)"); - } - return ESP_OK; - } - - if (strcmp(param, "buffer_headroom") == 0) { - long v = strtol(valstr, NULL, 10); - ESP_LOGI(TAG, "%s: Setting buffer_headroom to: %ld", __func__, v); - if (v >= 0 && v <= 100 && settings_set_buffer_headroom((int32_t)v) == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "400 Bad Request"); - httpd_resp_sendstr(req, "Invalid value (range: 0-100)"); - } - return ESP_OK; - } - // Parse integer value; strtol skips leading whitespace long v = strtol(valstr, NULL, 10); urlBuf.int_value = (int32_t)v; @@ -423,91 +328,6 @@ static esp_err_t root_delete_handler(httpd_req_t *req) { return ESP_OK; } - // WiFi Resilience Settings - clear handlers - if (strcmp(param, "tcp_nodelay") == 0) { - ESP_LOGI(TAG, "%s: Clearing tcp_nodelay from NVS", __func__); - if (settings_clear_tcp_nodelay() == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "500 Internal Server Error"); - httpd_resp_sendstr(req, "error"); - } - return ESP_OK; - } - - if (strcmp(param, "queue_empty_threshold") == 0) { - ESP_LOGI(TAG, "%s: Clearing queue_empty_threshold from NVS", __func__); - if (settings_clear_queue_empty_threshold() == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "500 Internal Server Error"); - httpd_resp_sendstr(req, "error"); - } - return ESP_OK; - } - - if (strcmp(param, "queue_insert_timeout") == 0) { - ESP_LOGI(TAG, "%s: Clearing queue_insert_timeout from NVS", __func__); - if (settings_clear_queue_insert_timeout() == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "500 Internal Server Error"); - httpd_resp_sendstr(req, "error"); - } - return ESP_OK; - } - - if (strcmp(param, "fast_sync_latency") == 0) { - ESP_LOGI(TAG, "%s: Clearing fast_sync_latency from NVS", __func__); - if (settings_clear_fast_sync_latency() == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "500 Internal Server Error"); - httpd_resp_sendstr(req, "error"); - } - return ESP_OK; - } - - if (strcmp(param, "reconnect_min_delay") == 0) { - ESP_LOGI(TAG, "%s: Clearing reconnect_min_delay from NVS", __func__); - if (settings_clear_reconnect_min_delay() == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "500 Internal Server Error"); - httpd_resp_sendstr(req, "error"); - } - return ESP_OK; - } - - if (strcmp(param, "reconnect_max_delay") == 0) { - ESP_LOGI(TAG, "%s: Clearing reconnect_max_delay from NVS", __func__); - if (settings_clear_reconnect_max_delay() == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "500 Internal Server Error"); - httpd_resp_sendstr(req, "error"); - } - return ESP_OK; - } - - if (strcmp(param, "buffer_headroom") == 0) { - ESP_LOGI(TAG, "%s: Clearing buffer_headroom from NVS", __func__); - if (settings_clear_buffer_headroom() == ESP_OK) { - httpd_resp_set_status(req, "200 OK"); - httpd_resp_sendstr(req, "ok"); - } else { - httpd_resp_set_status(req, "500 Internal Server Error"); - httpd_resp_sendstr(req, "error"); - } - return ESP_OK; - } - // Unknown parameter httpd_resp_set_status(req, "400 Bad Request"); httpd_resp_sendstr(req, "Unknown parameter"); @@ -748,61 +568,12 @@ static esp_err_t get_capabilities_handler(httpd_req_t *req) { httpd_resp_sendstr(req, "{\"dsp_enabled\": false}"); #endif - } else if (strcmp(tab, "advanced") == 0) { - // Return WiFi Resilience / Advanced settings - char json_buf[512]; - int len = 0; - - // Get all WiFi resilience settings - bool tcp_nodelay = true; - int32_t queue_empty_threshold = 3; - int32_t queue_insert_timeout = 50; - int32_t fast_sync_latency = 50000; - int32_t reconnect_min_delay = 1000; - int32_t reconnect_max_delay = 30000; - int32_t buffer_headroom = 50; - - settings_get_tcp_nodelay(&tcp_nodelay); - settings_get_queue_empty_threshold(&queue_empty_threshold); - settings_get_queue_insert_timeout(&queue_insert_timeout); - settings_get_fast_sync_latency(&fast_sync_latency); - settings_get_reconnect_min_delay(&reconnect_min_delay); - settings_get_reconnect_max_delay(&reconnect_max_delay); - settings_get_buffer_headroom(&buffer_headroom); - - len = snprintf(json_buf, sizeof(json_buf), - "{\"tcp_nodelay\":%s," - "\"queue_empty_threshold\":%d," - "\"queue_insert_timeout\":%d," - "\"fast_sync_latency\":%d," - "\"reconnect_min_delay\":%d," - "\"reconnect_max_delay\":%d," - "\"buffer_headroom\":%d}", - tcp_nodelay ? "true" : "false", - (int)queue_empty_threshold, - (int)queue_insert_timeout, - (int)fast_sync_latency, - (int)reconnect_min_delay, - (int)reconnect_max_delay, - (int)buffer_headroom); - - if (len >= (int)sizeof(json_buf)) { - ESP_LOGE(TAG, "%s: JSON buffer overflow", __func__); - httpd_resp_set_status(req, "500 Internal Server Error"); - httpd_resp_sendstr(req, "{\"error\": \"Buffer overflow\"}"); - return ESP_OK; - } - - httpd_resp_set_status(req, "200 OK"); - httpd_resp_set_type(req, "application/json"); - httpd_resp_sendstr(req, json_buf); - } else { // Unknown tab ESP_LOGW(TAG, "%s: Unknown tab: %s", __func__, tab); httpd_resp_set_status(req, "400 Bad Request"); httpd_resp_sendstr( - req, "{\"error\": \"Unknown tab. Use ?tab=general, ?tab=dsp, or ?tab=advanced\"}"); + req, "{\"error\": \"Unknown tab. Use ?tab=general or ?tab=dsp\"}"); } return ESP_OK; diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index b006318b..01cd150e 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -60,15 +60,6 @@ menu "WiFi Resilience Settings" Disable Nagle's algorithm for lower latency on TCP connections. Recommended for real-time audio streaming. - config WIFI_FAST_SYNC_LATENCY_US - int "Fast sync latency buffer (µs)" - range 10000 100000 - default 50000 - help - Time tolerance for fast sync operations in microseconds. - Higher values are more tolerant of WiFi latency jitter. - Default: 50000 (50ms) - config WIFI_RECONNECT_MIN_DELAY_MS int "Reconnect minimum delay (ms)" range 100 5000