Compare commits

...
7 changed files with 151 additions and 0 deletions
+25
View File
@@ -26,6 +26,16 @@ _LOGGER = logging.getLogger(__name__)
# Components can request high performance networking and this configures lwip and WiFi settings
KEY_HIGH_PERFORMANCE_NETWORKING = "high_performance_networking"
CONF_ENABLE_HIGH_PERFORMANCE = "enable_high_performance"
CONF_TCP_SEND_BUFFER = "tcp_send_buffer"
# lwIP queues at most this many unsent/unacked bytes per TCP socket; the
# stock ESP-IDF default (5744 bytes) stalls bursty senders like a Bluetooth
# proxy streaming GATT notifications. Bounds follow the lwIP guidance for the
# default 1440 byte MSS: at least 2 x MSS, at most 65535 without window
# scaling. The cap is kept even when window scaling is on (high performance
# with PSRAM) as a deliberate conservative bound.
TCP_SEND_BUFFER_MIN = 2880
TCP_SEND_BUFFER_MAX = 65535
# Network priority tracking infrastructure
# Components can query this to determine their relative setup priority.
@@ -306,6 +316,11 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ENABLE_HIGH_PERFORMANCE): cv.All(
cv.boolean, cv.only_on_esp32
),
cv.Optional(CONF_TCP_SEND_BUFFER): cv.All(
cv.validate_bytes,
cv.int_range(min=TCP_SEND_BUFFER_MIN, max=TCP_SEND_BUFFER_MAX),
cv.only_on_esp32,
),
cv.Optional(CONF_PRIORITY): _validate_priority_list,
}
),
@@ -446,6 +461,16 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64)
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64)
# After the high performance block so an explicit size wins over the
# bundle's 65534 (last write wins in the sdkconfig store).
if (tcp_send_buffer := config.get(CONF_TCP_SEND_BUFFER)) is not None:
if CORE.is_esp32 and should_enable:
_LOGGER.info(
"TCP send buffer set to %d bytes by configuration (overriding high performance value)",
tcp_send_buffer,
)
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", tcp_send_buffer)
if CORE.is_nrf52:
zephyr_add_prj_conf("NETWORKING", True)
zephyr_add_prj_conf("NET_IPV6", True)
@@ -6,6 +6,7 @@
#include "esp_err.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "lwip/opt.h"
#ifdef USE_NETWORK_DEFAULT_ROUTE
#include "esphome/core/application.h"
@@ -43,6 +44,15 @@ void NetworkComponent::setup() {
}
}
void NetworkComponent::dump_config() {
// The effective compile-time lwIP value, so the log reflects tcp_send_buffer
// or the high performance bundle when either changed it.
ESP_LOGCONFIG(TAG,
"Network:\n"
" TCP send buffer: %d bytes",
TCP_SND_BUF);
}
#ifdef USE_NETWORK_DEFAULT_ROUTE
static esp_netif_t *connected_wifi_netif() {
#ifdef USE_WIFI
@@ -13,6 +13,7 @@ namespace esphome::network {
class NetworkComponent final : public Component {
public:
void setup() override;
void dump_config() override;
// AFTER_BLUETOOTH: BLE controller must initialize before esp_netif_init per IDF guidance.
float get_setup_priority() const override { return setup_priority::AFTER_BLUETOOTH; }
@@ -0,0 +1,14 @@
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
wifi:
ssid: "test_ssid"
password: "test_password"
network:
tcp_send_buffer: 32kB
@@ -0,0 +1,15 @@
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
wifi:
ssid: "test_ssid"
password: "test_password"
network:
enable_high_performance: true
tcp_send_buffer: 16384
@@ -0,0 +1,85 @@
"""Tests for the ``network: tcp_send_buffer:`` option.
The option sets lwIP's per-socket TCP send buffer
(CONFIG_LWIP_TCP_SND_BUF_DEFAULT) on ESP-IDF. The stock default (5744 bytes)
stalls bursty senders such as a Bluetooth proxy streaming GATT notifications;
until now the only way to raise it was the all-or-nothing
``enable_high_performance`` bundle.
"""
from collections.abc import Callable
from pathlib import Path
import pytest
from voluptuous import Invalid
from esphome import config_validation as cv
from esphome.components.esp32.const import (
KEY_SDKCONFIG_OPTIONS,
KEY_VARIANT,
VARIANT_ESP32,
)
from esphome.components.network import (
CONF_TCP_SEND_BUFFER,
CONFIG_SCHEMA,
TCP_SEND_BUFFER_MAX,
TCP_SEND_BUFFER_MIN,
)
from esphome.const import KEY_ESP32, KEY_FRAMEWORK_VERSION, PlatformFramework
from esphome.core import CORE
from tests.component_tests.types import SetCoreConfigCallable
def _sdkconfig_option(name: str) -> int | None:
return CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS].get(name)
def test_tcp_send_buffer_sets_sdkconfig(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
generate_main(component_config_path("tcp_send_buffer.yaml"))
assert _sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT") == 32000
def test_tcp_send_buffer_overrides_high_performance(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""An explicit size wins over the high performance bundle's 65534."""
generate_main(component_config_path("tcp_send_buffer_high_perf.yaml"))
assert _sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT") == 16384
@pytest.mark.parametrize("value", [TCP_SEND_BUFFER_MIN, TCP_SEND_BUFFER_MAX])
def test_boundary_values_accepted(
set_core_config: SetCoreConfigCallable, value: int
) -> None:
set_core_config(
PlatformFramework.ESP32_IDF,
core_data={KEY_FRAMEWORK_VERSION: cv.Version(5, 5, 5)},
platform_data={KEY_VARIANT: VARIANT_ESP32},
)
assert CONFIG_SCHEMA({"tcp_send_buffer": value})[CONF_TCP_SEND_BUFFER] == value
@pytest.mark.parametrize("value", ["1kB", "128kB"])
def test_out_of_range_rejected(
set_core_config: SetCoreConfigCallable, value: str
) -> None:
set_core_config(
PlatformFramework.ESP32_IDF,
core_data={KEY_FRAMEWORK_VERSION: cv.Version(5, 5, 5)},
platform_data={KEY_VARIANT: VARIANT_ESP32},
)
with pytest.raises(Invalid):
CONFIG_SCHEMA({"tcp_send_buffer": value})
def test_rejected_on_esp8266(set_core_config: SetCoreConfigCallable) -> None:
set_core_config(
PlatformFramework.ESP8266_ARDUINO,
core_data={KEY_FRAMEWORK_VERSION: cv.Version(3, 1, 2)},
)
with pytest.raises(Invalid, match="esp32"):
CONFIG_SCHEMA({"tcp_send_buffer": "32kB"})
@@ -2,3 +2,4 @@
network:
enable_high_performance: true
tcp_send_buffer: 32kB