Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/network_interface/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
idf_component_register(SRCS "network_interface.c" "eth_interface.c" "wifi_interface.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES driver esp_wifi esp_eth esp_netif esp_timer nvs_flash improv_wifi)
PRIV_REQUIRES driver esp_wifi esp_eth esp_netif esp_timer nvs_flash improv_wifi settings_manager)
24 changes: 24 additions & 0 deletions components/network_interface/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,34 @@ menu "Wifi Configuration"
bool "OWE"
endchoice

config SNAPCLIENT_WIFI_PS_NONE
bool "Disable WiFi power save"
default y
help
Call esp_wifi_set_ps(WIFI_PS_NONE) after starting WiFi.

ESP-IDF defaults to WIFI_PS_MIN_MODEM, which sleeps the modem
between DTIM beacons. That adds roughly 100-300ms of latency to
either leg of a snapcast time-sync exchange, which is far more than
the protocol tolerates: sync round-trips stop completing, the
client/server offset freezes, and the player resyncs hard forever.

Disabling power save fixes that at the cost of higher idle power
draw. Say n only on battery-powered builds that do not need sync.

config WIFI_MAXIMUM_RETRY
int "Maximum connection retry"
default 5
help
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
Set to 0 if unlimited reconnections are preferred.

config SNAPCLIENT_WIFI_TX_POWER_CONTROL
bool "Enable WiFi TX power control"
default n
help
Expose a configurable WiFi transmit power setting (8.5–20 dBm).
The value is persisted to NVS and applied at boot. On some boards,
WiFi TX interferes with RX, so reducing TX power can improve
reception.
endmenu
39 changes: 36 additions & 3 deletions components/network_interface/wifi_interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "network_interface.h"
#include "nvs_flash.h"
#include "sdkconfig.h"
#include "settings_manager.h"

#if ENABLE_WIFI_PROVISIONING
#include "wifi_provisioning.h"
Expand Down Expand Up @@ -196,9 +197,6 @@ void wifi_start(void) {
esp_wifi_netif = esp_netif_create_wifi(WIFI_IF_STA, &esp_netif_config);
esp_wifi_set_default_wifi_sta_handlers();

// esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
// esp_wifi_set_ps(WIFI_PS_NONE);

#if ENABLE_WIFI_PROVISIONING
/* Start Wi-Fi station */
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
Expand All @@ -222,6 +220,15 @@ void wifi_start(void) {

ESP_ERROR_CHECK(esp_wifi_start());

#ifdef CONFIG_SNAPCLIENT_WIFI_TX_POWER_CONTROL
{
int32_t tx_raw = 80;
settings_get_wifi_tx_power(&tx_raw);
esp_wifi_set_max_tx_power((int8_t)tx_raw);
ESP_LOGI(TAG, "WiFi TX power set to %.2f dBm (raw %ld)", (float)tx_raw / 4.0f, (long)tx_raw);
}
#endif

ESP_LOGI(TAG, "Starting provisioning");

improv_init();
Expand Down Expand Up @@ -264,7 +271,33 @@ void wifi_start(void) {

ESP_ERROR_CHECK(esp_wifi_start());

#ifdef CONFIG_SNAPCLIENT_WIFI_TX_POWER_CONTROL
{
int32_t tx_raw = 80;
settings_get_wifi_tx_power(&tx_raw);
esp_wifi_set_max_tx_power((int8_t)tx_raw);
ESP_LOGI(TAG, "WiFi TX power set to %.2f dBm (raw %ld)", (float)tx_raw / 4.0f, (long)tx_raw);
}
#endif

ESP_LOGI(TAG, "wifi_init_sta finished. Trying to connect to %s",
wifi_config.sta.ssid);
#endif

/* Common to both paths above: both start Wi-Fi, and IDF defaults to
WIFI_PS_MIN_MODEM, whose beacon-interval modem sleep stalls snapcast
time-sync round-trips. */
#ifdef CONFIG_SNAPCLIENT_WIFI_PS_NONE
const wifi_ps_type_t ps_type = WIFI_PS_NONE;
#else
const wifi_ps_type_t ps_type = WIFI_PS_MIN_MODEM;
#endif
esp_err_t ps_err = esp_wifi_set_ps(ps_type);
if (ps_err != ESP_OK) {
ESP_LOGW(TAG, "esp_wifi_set_ps(%d) failed: %s", (int)ps_type,
esp_err_to_name(ps_err));
} else {
ESP_LOGI(TAG, "wifi power save: %s",
(ps_type == WIFI_PS_NONE) ? "none" : "min modem");
}
}
6 changes: 6 additions & 0 deletions components/settings_manager/include/settings_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ 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);

#ifdef CONFIG_SNAPCLIENT_WIFI_TX_POWER_CONTROL
/* WiFi TX power in ESP-IDF units (0.25 dBm each, range 34–80, default 80 = 20 dBm) */
esp_err_t settings_get_wifi_tx_power(int32_t *power_raw);
esp_err_t settings_set_wifi_tx_power(int32_t power_raw);
#endif

/**
* Get all settings as a JSON string
* @param json_out Buffer to store JSON string (caller must allocate)
Expand Down
63 changes: 63 additions & 0 deletions components/settings_manager/settings_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ static const char *NVS_KEY_HOSTNAME = "hostname";
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
static const char *NVS_KEY_WIFI_TX_POWER = "wifi_tx_pwr"; // int32, ESP-IDF units (0.25 dBm each)

// Mutex for thread-safe NVS access
static SemaphoreHandle_t hostname_mutex = NULL;
Expand Down Expand Up @@ -469,6 +470,60 @@ esp_err_t settings_clear_server_port(void) {
return err;
}

#ifdef CONFIG_SNAPCLIENT_WIFI_TX_POWER_CONTROL
esp_err_t settings_get_wifi_tx_power(int32_t *power_raw) {
ESP_LOGD(TAG, "%s: entered", __func__);
if (!power_raw) 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_WIFI_TX_POWER, &v);
nvs_close(h);
if (err == ESP_OK) {
*power_raw = v;
ESP_LOGD(TAG, "%s: wifi_tx_power from NVS: %.2f dBm (raw %ld)", __func__, (float)v / 4.0f, (long)v);
xSemaphoreGive(hostname_mutex);
return ESP_OK;
}
if (err != ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGW(TAG, "%s: NVS read error: %s", __func__, esp_err_to_name(err));
}
}
*power_raw = 80; // default: 20 dBm (ESP-IDF max)
xSemaphoreGive(hostname_mutex);
return ESP_OK;
}

esp_err_t settings_set_wifi_tx_power(int32_t power_raw) {
ESP_LOGD(TAG, "%s: power_raw=%ld (%.2f dBm)", __func__, (long)power_raw, (float)power_raw / 4.0f);
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_WIFI_TX_POWER, power_raw);
if (err == ESP_OK) err = nvs_commit(h);
nvs_close(h);
xSemaphoreGive(hostname_mutex);
if (err == ESP_OK) {
ESP_LOGI(TAG, "%s: wifi_tx_power saved: %.2f dBm (raw %ld)", __func__, (float)power_raw / 4.0f, (long)power_raw);
} else {
ESP_LOGE(TAG, "%s: Failed to save wifi_tx_power: %s", __func__, esp_err_to_name(err));
}
return err;
}
#endif // CONFIG_SNAPCLIENT_WIFI_TX_POWER_CONTROL

esp_err_t settings_get_json(char *json_out, size_t max_len) {
ESP_LOGD(TAG, "%s: entered", __func__);

Expand Down Expand Up @@ -504,6 +559,14 @@ esp_err_t settings_get_json(char *json_out, size_t max_len) {
cJSON_AddNumberToObject(root, "server_port", port);
}

#ifdef CONFIG_SNAPCLIENT_WIFI_TX_POWER_CONTROL
// Get WiFi TX power (raw ESP-IDF units, 0.25 dBm each)
int32_t tx_power = 80;
if (settings_get_wifi_tx_power(&tx_power) == ESP_OK) {
cJSON_AddNumberToObject(root, "wifi_tx_power", tx_power);
}
#endif

// Add DSP availability flag
#if CONFIG_USE_DSP_PROCESSOR
cJSON_AddBoolToObject(root, "dsp_available", true);
Expand Down
2 changes: 1 addition & 1 deletion components/ui_http_server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
idf_component_register(SRCS "ui_http_server.c"
INCLUDE_DIRS "include"
REQUIRES esp_http_server settings_manager dsp_processor_settings tas5805m_settings
PRIV_REQUIRES custom_board
PRIV_REQUIRES custom_board esp_wifi
EMBED_TXTFILES html/index.html
html/index.js
html/settings-ui.js
Expand Down
34 changes: 33 additions & 1 deletion components/ui_http_server/html/general-settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ <h1>General Settings</h1>
let currentSnapUseMDNS = true;
let currentSnapHost = '';
let currentSnapPort = '';
let currentTxPower = 80;
let hasTxPowerControl = false;
let needsRestart = false; // Track if device needs restart
let isDirty = false;

Expand All @@ -202,6 +204,8 @@ <h1>General Settings</h1>
currentSnapUseMDNS = capabilities.mdns_enabled !== undefined ? capabilities.mdns_enabled : true;
currentSnapHost = capabilities.server_host || '';
currentSnapPort = capabilities.server_port || '';
hasTxPowerControl = capabilities.wifi_tx_power !== undefined;
currentTxPower = hasTxPowerControl ? capabilities.wifi_tx_power : 80;

renderUI();
} catch (error) {
Expand Down Expand Up @@ -252,8 +256,22 @@ <h1>General Settings</h1>
html += '<button class="btn btn-danger" id="restart-btn-snap" onclick="requestRestart()" style="background-color:#e74c3c;" disabled>Restart</button>';
html += '</div>';
html += '</div>'; // .setting-control.setting-section

// WiFi TX power slider (only shown when firmware was built with TX power control)
if (hasTxPowerControl) {
html += '<div class="setting-control">';
html += '<label for="wifi-tx-power">WiFi TX Power</label>';
html += '<div class="description">Reduce transmit power to limit interference. Default (20 dBm) gives maximum range. Takes effect immediately.</div>';
html += '<div class="param-info" style="display:flex;justify-content:space-between;margin-bottom:6px;">';
html += `<span id="wifi-tx-power-value" style="font-weight:bold;">${(currentTxPower / 4).toFixed(1)} dBm</span>`;
html += '</div>';
html += `<input type="range" id="wifi-tx-power" min="34" max="80" step="2" value="${currentTxPower}" oninput="handleTxPowerChange(this.value)" style="width:100%;" aria-label="WiFi TX Power">`;
html += '<div style="display:flex;justify-content:space-between;font-size:12px;color:#888;margin-top:4px;"><span>8.5 dBm</span><span>20.0 dBm (default)</span></div>';
html += '</div>';
}

html += '</div>';

// Info message
html += '<div class="info">';
html += '<strong>Note:</strong> After changing the settings, you will need to restart the device for the changes to take effect.';
Expand Down Expand Up @@ -517,6 +535,20 @@ <h1>General Settings</h1>
});
}

// Handle WiFi TX power slider with debouncing
let txPowerTimer = null;
function handleTxPowerChange(value) {
const raw = parseInt(value, 10);
const span = document.getElementById('wifi-tx-power-value');
if (span) span.textContent = `${(raw / 4).toFixed(1)} dBm`;
if (txPowerTimer) clearTimeout(txPowerTimer);
txPowerTimer = setTimeout(() => {
setParameter('wifi_tx_power', raw);
currentTxPower = raw;
txPowerTimer = null;
}, 300);
}

// Initialize on page load
document.addEventListener('DOMContentLoaded', loadSettings);
</script>
Expand Down
15 changes: 15 additions & 0 deletions components/ui_http_server/ui_http_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "settings_manager.h"
#include "esp_wifi.h"

#if CONFIG_DAC_TAS5805M
#include "tas5805m_settings.h"
Expand Down Expand Up @@ -1251,6 +1252,20 @@ static void http_server_task(void *pvParameters) {
continue;
}

#ifdef CONFIG_SNAPCLIENT_WIFI_TX_POWER_CONTROL
if (strcmp(urlBuf.key, "wifi_tx_power") == 0) {
int32_t raw = urlBuf.int_value;
if (raw < 34) raw = 34; // floor: 8.5 dBm
if (raw > 80) raw = 80; // ceiling: 20 dBm
settings_set_wifi_tx_power(raw);
esp_wifi_set_max_tx_power((int8_t)raw);
ESP_LOGI(TAG, "%s: wifi_tx_power set to %.2f dBm (raw %ld)",
__func__, (float)raw / 4.0f, (long)raw);
continue;
}
#endif


// Handle parameter updates for current flow
bool param_recognized = false;

Expand Down