mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 12:17:23 +00:00
[esp32] Consolidate network/coexistence sdkconfig into a single reconciler (#17008)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,7 @@ from .const import (
|
|||||||
KEY_FLASH_SIZE,
|
KEY_FLASH_SIZE,
|
||||||
KEY_FULL_CERT_BUNDLE,
|
KEY_FULL_CERT_BUNDLE,
|
||||||
KEY_IDF_VERSION,
|
KEY_IDF_VERSION,
|
||||||
|
KEY_NETWORK_SDKCONFIG,
|
||||||
KEY_PATH,
|
KEY_PATH,
|
||||||
KEY_REF,
|
KEY_REF,
|
||||||
KEY_REPO,
|
KEY_REPO,
|
||||||
@@ -597,6 +598,59 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
|
|||||||
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value
|
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NetworkSdkconfigData:
|
||||||
|
"""Inputs for the network-related esp32 sdkconfig flags, reconciled at FINAL.
|
||||||
|
|
||||||
|
Components call the request_*() helpers below (and esp32's own to_code fills
|
||||||
|
in enable_lwip_dhcp_server) instead of setting the WiFi/Ethernet/Bluetooth
|
||||||
|
sdkconfig flags directly; the single _reconcile_network_sdkconfig() coroutine
|
||||||
|
then decides the final values so they no longer depend on call order.
|
||||||
|
"""
|
||||||
|
|
||||||
|
wifi: bool = False # WiFi component active (STA and/or AP)
|
||||||
|
wifi_ap: bool = False # WiFi AP mode configured
|
||||||
|
ethernet: bool = False # Ethernet component active
|
||||||
|
bluetooth: bool = False # any BLE component active
|
||||||
|
ble_42: bool = False # BLE 4.2 features needed
|
||||||
|
software_coexistence: bool = False # WiFi/BT software coexistence requested
|
||||||
|
# esp32 advanced enable_lwip_dhcp_server option (True/False/None=unset)
|
||||||
|
enable_lwip_dhcp_server: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def _network_sdkconfig() -> NetworkSdkconfigData:
|
||||||
|
data = CORE.data[KEY_ESP32]
|
||||||
|
if KEY_NETWORK_SDKCONFIG not in data:
|
||||||
|
data[KEY_NETWORK_SDKCONFIG] = NetworkSdkconfigData()
|
||||||
|
return data[KEY_NETWORK_SDKCONFIG]
|
||||||
|
|
||||||
|
|
||||||
|
def request_wifi(ap: bool = False) -> None:
|
||||||
|
"""Request the WiFi stack. Pass ap=True when AP mode is configured."""
|
||||||
|
net = _network_sdkconfig()
|
||||||
|
net.wifi = True
|
||||||
|
if ap:
|
||||||
|
net.wifi_ap = True
|
||||||
|
|
||||||
|
|
||||||
|
def request_ethernet() -> None:
|
||||||
|
"""Request the Ethernet stack."""
|
||||||
|
_network_sdkconfig().ethernet = True
|
||||||
|
|
||||||
|
|
||||||
|
def request_bluetooth(ble_42: bool = False) -> None:
|
||||||
|
"""Request the Bluetooth controller. Pass ble_42=True for 4.2 features."""
|
||||||
|
net = _network_sdkconfig()
|
||||||
|
net.bluetooth = True
|
||||||
|
if ble_42:
|
||||||
|
net.ble_42 = True
|
||||||
|
|
||||||
|
|
||||||
|
def request_software_coexistence() -> None:
|
||||||
|
"""Request WiFi/BT software coexistence (only valid alongside WiFi)."""
|
||||||
|
_network_sdkconfig().software_coexistence = True
|
||||||
|
|
||||||
|
|
||||||
def add_idf_component(
|
def add_idf_component(
|
||||||
*,
|
*,
|
||||||
name: str,
|
name: str,
|
||||||
@@ -1847,6 +1901,61 @@ async def _set_libc_picolibc_newlib_compat() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(CoroPriority.FINAL)
|
||||||
|
async def _reconcile_network_sdkconfig() -> None:
|
||||||
|
"""Reconcile WiFi/Ethernet/Bluetooth/coexistence sdkconfig flags.
|
||||||
|
|
||||||
|
Single decision point for flags that multiple components used to set
|
||||||
|
directly (and sometimes with conflicting values). Runs at FINAL priority so
|
||||||
|
every request_*() call (made from the various components' to_code at their
|
||||||
|
own priorities) is seen first. A user-supplied sdkconfig_options value
|
||||||
|
always takes precedence.
|
||||||
|
"""
|
||||||
|
net = CORE.data[KEY_ESP32].get(KEY_NETWORK_SDKCONFIG, NetworkSdkconfigData())
|
||||||
|
opts = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
|
||||||
|
is_arduino = CORE.using_arduino
|
||||||
|
|
||||||
|
def set_opt(name: str, value: SdkconfigValueType) -> None:
|
||||||
|
# User sdkconfig_options (applied during to_code) win.
|
||||||
|
if name not in opts:
|
||||||
|
add_idf_sdkconfig_option(name, value)
|
||||||
|
|
||||||
|
# Bluetooth: only ever enable when requested. The IDF default is off and
|
||||||
|
# nothing sets these False today, so never write False here.
|
||||||
|
if net.bluetooth:
|
||||||
|
set_opt("CONFIG_BT_ENABLED", True)
|
||||||
|
if net.ble_42:
|
||||||
|
set_opt("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
|
||||||
|
|
||||||
|
# WiFi stack: disable only when Ethernet is present and WiFi is not. WiFi
|
||||||
|
# relies on the IDF default (enabled), so it is never written True here.
|
||||||
|
wifi_disabled = net.ethernet and not net.wifi
|
||||||
|
if wifi_disabled:
|
||||||
|
set_opt("CONFIG_ESP_WIFI_ENABLED", False)
|
||||||
|
|
||||||
|
# Software coexistence: enable when requested (the schema only allows it
|
||||||
|
# alongside WiFi). Disable only in the Ethernet-without-WiFi case.
|
||||||
|
if net.software_coexistence:
|
||||||
|
set_opt("CONFIG_SW_COEXIST_ENABLE", True)
|
||||||
|
elif wifi_disabled:
|
||||||
|
set_opt("CONFIG_SW_COEXIST_ENABLE", False)
|
||||||
|
|
||||||
|
# SoftAP support: drop it when WiFi is used without AP mode (IDF only).
|
||||||
|
if not is_arduino and net.wifi and not net.wifi_ap:
|
||||||
|
set_opt("CONFIG_ESP_WIFI_SOFTAP_SUPPORT", False)
|
||||||
|
|
||||||
|
# LWIP DHCP server: a WiFi-AP-mode / enable_lwip_dhcp_server concern (not
|
||||||
|
# coexistence). Disable when WiFi has no AP (IDF) or the enable_lwip_dhcp_server
|
||||||
|
# option is set to false, unless Arduino+Ethernet needs the symbols to compile.
|
||||||
|
wifi_wants_dhcps_off = not is_arduino and net.wifi and not net.wifi_ap
|
||||||
|
dhcp_server_disabled_by_option = net.enable_lwip_dhcp_server is False
|
||||||
|
arduino_eth_exclusion = is_arduino and net.ethernet
|
||||||
|
if (
|
||||||
|
wifi_wants_dhcps_off or dhcp_server_disabled_by_option
|
||||||
|
) and not arduino_eth_exclusion:
|
||||||
|
set_opt("CONFIG_LWIP_DHCPS", False)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.FINAL)
|
@coroutine_with_priority(CoroPriority.FINAL)
|
||||||
async def _add_yaml_idf_components(components: list[ConfigType]):
|
async def _add_yaml_idf_components(components: list[ConfigType]):
|
||||||
"""Add IDF components from YAML config with final priority to override code-added components."""
|
"""Add IDF components from YAML config with final priority to override code-added components."""
|
||||||
@@ -2171,14 +2280,12 @@ async def to_code(config):
|
|||||||
for component_name in advanced.get(CONF_INCLUDE_BUILTIN_IDF_COMPONENTS, []):
|
for component_name in advanced.get(CONF_INCLUDE_BUILTIN_IDF_COMPONENTS, []):
|
||||||
include_builtin_idf_component(component_name)
|
include_builtin_idf_component(component_name)
|
||||||
|
|
||||||
# DHCP server: only disable if explicitly set to false
|
# DHCP server (CONFIG_LWIP_DHCPS) is reconciled in _reconcile_network_sdkconfig
|
||||||
# WiFi component handles its own optimization when AP mode is not used
|
# together with the WiFi component's own AP-mode optimization; record the user's
|
||||||
# When using Arduino with Ethernet, DHCP server functions must be available
|
# advanced tristate (True/False/None) for it to consume at FINAL priority.
|
||||||
# for the Network library to compile, even if not actively used
|
_network_sdkconfig().enable_lwip_dhcp_server = advanced.get(
|
||||||
if advanced.get(CONF_ENABLE_LWIP_DHCP_SERVER) is False and not (
|
CONF_ENABLE_LWIP_DHCP_SERVER
|
||||||
conf[CONF_TYPE] == FRAMEWORK_ARDUINO and "ethernet" in CORE.loaded_integrations
|
)
|
||||||
):
|
|
||||||
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
|
|
||||||
if not advanced[CONF_ENABLE_LWIP_MDNS_QUERIES]:
|
if not advanced[CONF_ENABLE_LWIP_MDNS_QUERIES]:
|
||||||
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
|
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
|
||||||
if not advanced[CONF_ENABLE_LWIP_BRIDGE_INTERFACE]:
|
if not advanced[CONF_ENABLE_LWIP_BRIDGE_INTERFACE]:
|
||||||
@@ -2397,6 +2504,9 @@ async def to_code(config):
|
|||||||
# FINAL priority: runs after every require_libc_picolibc_newlib_compat() call
|
# FINAL priority: runs after every require_libc_picolibc_newlib_compat() call
|
||||||
CORE.add_job(_set_libc_picolibc_newlib_compat)
|
CORE.add_job(_set_libc_picolibc_newlib_compat)
|
||||||
|
|
||||||
|
# FINAL priority: runs after every network/coexistence request_*() call
|
||||||
|
CORE.add_job(_reconcile_network_sdkconfig)
|
||||||
|
|
||||||
# Disable regi2c control functions in IRAM
|
# Disable regi2c control functions in IRAM
|
||||||
# Only needed if using analog peripherals (ADC, DAC, etc.) from ISRs while cache is disabled
|
# Only needed if using analog peripherals (ADC, DAC, etc.) from ISRs while cache is disabled
|
||||||
if advanced[CONF_DISABLE_REGI2C_IN_IRAM]:
|
if advanced[CONF_DISABLE_REGI2C_IN_IRAM]:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ KEY_SUBMODULES = "submodules"
|
|||||||
KEY_EXTRA_BUILD_FILES = "extra_build_files"
|
KEY_EXTRA_BUILD_FILES = "extra_build_files"
|
||||||
KEY_FULL_CERT_BUNDLE = "full_cert_bundle"
|
KEY_FULL_CERT_BUNDLE = "full_cert_bundle"
|
||||||
KEY_IDF_VERSION = "idf_version"
|
KEY_IDF_VERSION = "idf_version"
|
||||||
|
KEY_NETWORK_SDKCONFIG = "network_sdkconfig"
|
||||||
|
|
||||||
VARIANT_ESP32 = "ESP32"
|
VARIANT_ESP32 = "ESP32"
|
||||||
VARIANT_ESP32C2 = "ESP32C2"
|
VARIANT_ESP32C2 = "ESP32C2"
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ from typing import Any
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.const import CONF_USE_PSRAM
|
from esphome.components.const import CONF_USE_PSRAM
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
from esphome.components.esp32 import (
|
||||||
|
add_idf_sdkconfig_option,
|
||||||
|
const,
|
||||||
|
get_esp32_variant,
|
||||||
|
request_bluetooth,
|
||||||
|
)
|
||||||
from esphome.components.esp32.const import VARIANT_ESP32C2
|
from esphome.components.esp32.const import VARIANT_ESP32C2
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
@@ -599,8 +604,7 @@ async def to_code(config):
|
|||||||
max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
|
max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
|
||||||
cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections)
|
cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections)
|
||||||
|
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
request_bluetooth(ble_42=True)
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
|
|
||||||
|
|
||||||
# When PSRAM and BT are used together, Bluedroid should prefer SPIRAM for
|
# When PSRAM and BT are used together, Bluedroid should prefer SPIRAM for
|
||||||
# heap allocations and use dynamic (heap-based) environment memory tables
|
# heap allocations and use dynamic (heap-based) environment memory tables
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32_ble
|
from esphome.components import esp32_ble
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import request_bluetooth
|
||||||
from esphome.components.esp32_ble import CONF_BLE_ID
|
from esphome.components.esp32_ble import CONF_BLE_ID
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_TX_POWER, CONF_TYPE, CONF_UUID
|
from esphome.const import CONF_ID, CONF_TX_POWER, CONF_TYPE, CONF_UUID
|
||||||
@@ -86,5 +86,4 @@ async def to_code(config):
|
|||||||
|
|
||||||
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
||||||
|
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
request_bluetooth(ble_42=True)
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import encodings
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32_ble
|
from esphome.components import esp32_ble
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import request_bluetooth
|
||||||
from esphome.components.esp32_ble import BTLoggers, bt_uuid
|
from esphome.components.esp32_ble import BTLoggers, bt_uuid
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.config_validation import UNDEFINED
|
from esphome.config_validation import UNDEFINED
|
||||||
@@ -632,7 +632,7 @@ async def to_code(config):
|
|||||||
)
|
)
|
||||||
cg.add_define("USE_ESP32_BLE_SERVER")
|
cg.add_define("USE_ESP32_BLE_SERVER")
|
||||||
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
request_bluetooth()
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action(
|
@automation.register_action(
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import logging
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32_ble, ota
|
from esphome.components import esp32_ble, ota
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import (
|
||||||
|
add_idf_sdkconfig_option,
|
||||||
|
request_bluetooth,
|
||||||
|
request_software_coexistence,
|
||||||
|
)
|
||||||
from esphome.components.esp32_ble import (
|
from esphome.components.esp32_ble import (
|
||||||
IDF_MAX_CONNECTIONS,
|
IDF_MAX_CONNECTIONS,
|
||||||
BTLoggers,
|
BTLoggers,
|
||||||
@@ -315,9 +319,9 @@ async def to_code(config):
|
|||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
request_bluetooth()
|
||||||
if config.get(CONF_SOFTWARE_COEXISTENCE):
|
if config.get(CONF_SOFTWARE_COEXISTENCE):
|
||||||
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True)
|
request_software_coexistence()
|
||||||
# https://github.com/espressif/esp-idf/issues/4101
|
# https://github.com/espressif/esp-idf/issues/4101
|
||||||
# https://github.com/espressif/esp-idf/issues/2503
|
# https://github.com/espressif/esp-idf/issues/2503
|
||||||
# Match arduino CONFIG_BTU_TASK_STACK_SIZE
|
# Match arduino CONFIG_BTU_TASK_STACK_SIZE
|
||||||
|
|||||||
@@ -540,6 +540,7 @@ async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None:
|
|||||||
add_idf_sdkconfig_option,
|
add_idf_sdkconfig_option,
|
||||||
idf_version,
|
idf_version,
|
||||||
include_builtin_idf_component,
|
include_builtin_idf_component,
|
||||||
|
request_ethernet,
|
||||||
)
|
)
|
||||||
|
|
||||||
if config[CONF_TYPE] in SPI_ETHERNET_TYPES:
|
if config[CONF_TYPE] in SPI_ETHERNET_TYPES:
|
||||||
@@ -586,10 +587,9 @@ async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None:
|
|||||||
)
|
)
|
||||||
cg.add(var.add_phy_register(reg))
|
cg.add(var.add_phy_register(reg))
|
||||||
|
|
||||||
# Disable WiFi when using Ethernet to save memory
|
# Register Ethernet with the esp32 sdkconfig reconciler, which disables the
|
||||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False)
|
# WiFi stack and WiFi/BT coexistence when Ethernet is used without WiFi.
|
||||||
# Also disable WiFi/BT coexistence since WiFi is disabled
|
request_ethernet()
|
||||||
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False)
|
|
||||||
|
|
||||||
# Re-enable ESP-IDF's Ethernet driver (excluded by default to save compile time)
|
# Re-enable ESP-IDF's Ethernet driver (excluded by default to save compile time)
|
||||||
include_builtin_idf_component("esp_eth")
|
include_builtin_idf_component("esp_eth")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from esphome.components.esp32 import (
|
|||||||
const,
|
const,
|
||||||
get_esp32_variant,
|
get_esp32_variant,
|
||||||
only_on_variant,
|
only_on_variant,
|
||||||
|
request_wifi,
|
||||||
)
|
)
|
||||||
from esphome.components.network import (
|
from esphome.components.network import (
|
||||||
has_high_performance_networking,
|
has_high_performance_networking,
|
||||||
@@ -594,9 +595,11 @@ async def to_code(config):
|
|||||||
)
|
)
|
||||||
cg.add(var.set_ap_timeout(conf[CONF_AP_TIMEOUT]))
|
cg.add(var.set_ap_timeout(conf[CONF_AP_TIMEOUT]))
|
||||||
cg.add_define("USE_WIFI_AP")
|
cg.add_define("USE_WIFI_AP")
|
||||||
elif CORE.is_esp32 and not CORE.using_arduino:
|
|
||||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_SOFTAP_SUPPORT", False)
|
# ESP32: register the WiFi stack with the esp32 sdkconfig reconciler, which
|
||||||
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
|
# drops SoftAP support / the LWIP DHCP server when AP mode is unused.
|
||||||
|
if CORE.is_esp32:
|
||||||
|
request_wifi(ap=CONF_AP in config)
|
||||||
|
|
||||||
# Disable Enterprise WiFi support if no EAP is configured
|
# Disable Enterprise WiFi support if no EAP is configured
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
esphome:
|
||||||
|
name: test
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32dev
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
ethernet:
|
||||||
|
type: W5500
|
||||||
|
clk_pin: 19
|
||||||
|
mosi_pin: 21
|
||||||
|
miso_pin: 23
|
||||||
|
cs_pin: 18
|
||||||
|
interrupt_pin: 36
|
||||||
|
reset_pin: 22
|
||||||
|
clock_speed: 10Mhz
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
esphome:
|
||||||
|
name: test
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32dev
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: "test_ssid"
|
||||||
|
password: "test_password"
|
||||||
|
|
||||||
|
esp32_ble_tracker:
|
||||||
|
software_coexistence: true
|
||||||
11
tests/component_tests/esp32/config/network_wifi_only.yaml
Normal file
11
tests/component_tests/esp32/config/network_wifi_only.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
esphome:
|
||||||
|
name: test
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32dev
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: "test_ssid"
|
||||||
|
password: "test_password"
|
||||||
@@ -2,14 +2,25 @@
|
|||||||
Test ESP32 configuration
|
Test ESP32 configuration
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from esphome.components.esp32 import VARIANT_ESP32, VARIANTS
|
from esphome.components.esp32 import (
|
||||||
from esphome.components.esp32.const import KEY_ESP32, KEY_SDKCONFIG_OPTIONS, KEY_VARIANT
|
VARIANT_ESP32,
|
||||||
|
VARIANTS,
|
||||||
|
NetworkSdkconfigData,
|
||||||
|
_reconcile_network_sdkconfig,
|
||||||
|
)
|
||||||
|
from esphome.components.esp32.const import (
|
||||||
|
KEY_ESP32,
|
||||||
|
KEY_NETWORK_SDKCONFIG,
|
||||||
|
KEY_SDKCONFIG_OPTIONS,
|
||||||
|
KEY_VARIANT,
|
||||||
|
)
|
||||||
from esphome.components.esp32.gpio import validate_gpio_pin
|
from esphome.components.esp32.gpio import validate_gpio_pin
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
@@ -343,3 +354,183 @@ def test_flash_mode_unset_leaves_defaults(
|
|||||||
assert not any(key.startswith("CONFIG_ESPTOOLPY_FLASHFREQ_") for key in sdkconfig)
|
assert not any(key.startswith("CONFIG_ESPTOOLPY_FLASHFREQ_") for key in sdkconfig)
|
||||||
assert "board_build.flash_mode" not in CORE.platformio_options
|
assert "board_build.flash_mode" not in CORE.platformio_options
|
||||||
assert "board_build.f_flash" not in CORE.platformio_options
|
assert "board_build.f_flash" not in CORE.platformio_options
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("framework", "net", "preset", "expected"),
|
||||||
|
[
|
||||||
|
# --- IDF: single-interface cases (must match pre-refactor behavior) ---
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
NetworkSdkconfigData(wifi=True),
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"CONFIG_ESP_WIFI_SOFTAP_SUPPORT": False,
|
||||||
|
"CONFIG_LWIP_DHCPS": False,
|
||||||
|
},
|
||||||
|
id="idf_wifi_no_ap",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
NetworkSdkconfigData(wifi=True, wifi_ap=True),
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
id="idf_wifi_ap_leaves_softap_dhcps",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
NetworkSdkconfigData(ethernet=True),
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"CONFIG_ESP_WIFI_ENABLED": False,
|
||||||
|
"CONFIG_SW_COEXIST_ENABLE": False,
|
||||||
|
},
|
||||||
|
id="idf_ethernet_only",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
NetworkSdkconfigData(
|
||||||
|
wifi=True, bluetooth=True, ble_42=True, software_coexistence=True
|
||||||
|
),
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"CONFIG_BT_ENABLED": True,
|
||||||
|
"CONFIG_BT_BLE_42_FEATURES_SUPPORTED": True,
|
||||||
|
"CONFIG_SW_COEXIST_ENABLE": True,
|
||||||
|
"CONFIG_ESP_WIFI_SOFTAP_SUPPORT": False,
|
||||||
|
"CONFIG_LWIP_DHCPS": False,
|
||||||
|
},
|
||||||
|
id="idf_wifi_ble_tracker_coexistence",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
NetworkSdkconfigData(bluetooth=True),
|
||||||
|
{},
|
||||||
|
{"CONFIG_BT_ENABLED": True},
|
||||||
|
id="idf_ble_server_only_no_ble42",
|
||||||
|
),
|
||||||
|
# --- IDF: user sdkconfig_options always win ---
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
NetworkSdkconfigData(wifi=True),
|
||||||
|
{"CONFIG_ESP_WIFI_SOFTAP_SUPPORT": True},
|
||||||
|
{
|
||||||
|
"CONFIG_ESP_WIFI_SOFTAP_SUPPORT": True,
|
||||||
|
"CONFIG_LWIP_DHCPS": False,
|
||||||
|
},
|
||||||
|
id="idf_user_override_wins",
|
||||||
|
),
|
||||||
|
# --- IDF: user advanced enable_lwip_dhcp_server: false, even with AP ---
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
NetworkSdkconfigData(
|
||||||
|
wifi=True, wifi_ap=True, enable_lwip_dhcp_server=False
|
||||||
|
),
|
||||||
|
{},
|
||||||
|
{"CONFIG_LWIP_DHCPS": False},
|
||||||
|
id="idf_user_disables_dhcps_with_ap",
|
||||||
|
),
|
||||||
|
# --- IDF: WiFi + Ethernet coexist (the multi-interface unlock) ---
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
NetworkSdkconfigData(wifi=True, ethernet=True),
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"CONFIG_ESP_WIFI_SOFTAP_SUPPORT": False,
|
||||||
|
"CONFIG_LWIP_DHCPS": False,
|
||||||
|
},
|
||||||
|
id="idf_wifi_and_ethernet_keeps_wifi_enabled",
|
||||||
|
),
|
||||||
|
# --- Arduino: SoftAP/DHCPS disable is IDF-only ---
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
NetworkSdkconfigData(wifi=True),
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
id="arduino_wifi_no_ap_untouched",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
NetworkSdkconfigData(ethernet=True),
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"CONFIG_ESP_WIFI_ENABLED": False,
|
||||||
|
"CONFIG_SW_COEXIST_ENABLE": False,
|
||||||
|
},
|
||||||
|
id="arduino_ethernet_only_disables_wifi",
|
||||||
|
),
|
||||||
|
# --- Arduino + Ethernet: DHCPS stays available even if user disabled it ---
|
||||||
|
pytest.param(
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
NetworkSdkconfigData(ethernet=True, enable_lwip_dhcp_server=False),
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"CONFIG_ESP_WIFI_ENABLED": False,
|
||||||
|
"CONFIG_SW_COEXIST_ENABLE": False,
|
||||||
|
},
|
||||||
|
id="arduino_ethernet_dhcps_exclusion",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_reconcile_network_sdkconfig(
|
||||||
|
set_core_config: SetCoreConfigCallable,
|
||||||
|
framework: PlatformFramework,
|
||||||
|
net: NetworkSdkconfigData,
|
||||||
|
preset: dict[str, Any],
|
||||||
|
expected: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""The FINAL-priority reconciler resolves WiFi/Ethernet/Bluetooth/coexistence
|
||||||
|
sdkconfig flags from the requests recorded in NetworkSdkconfigData."""
|
||||||
|
set_core_config(framework)
|
||||||
|
CORE.data[KEY_ESP32] = {
|
||||||
|
KEY_SDKCONFIG_OPTIONS: dict(preset),
|
||||||
|
KEY_NETWORK_SDKCONFIG: net,
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncio.run(_reconcile_network_sdkconfig())
|
||||||
|
|
||||||
|
assert CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_network_wifi_only_reconciles_end_to_end(
|
||||||
|
generate_main: Callable[[str | Path], str],
|
||||||
|
component_config_path: Callable[[str], Path],
|
||||||
|
) -> None:
|
||||||
|
"""End-to-end: codegen for an ESP-IDF WiFi (no AP) config runs the reconciler
|
||||||
|
after wifi's request_wifi(), disabling SoftAP support and the DHCP server."""
|
||||||
|
generate_main(component_config_path("network_wifi_only.yaml"))
|
||||||
|
sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
|
||||||
|
assert sdkconfig.get("CONFIG_ESP_WIFI_SOFTAP_SUPPORT") is False
|
||||||
|
assert sdkconfig.get("CONFIG_LWIP_DHCPS") is False
|
||||||
|
# WiFi stack stays enabled (no ethernet) and no Bluetooth requested.
|
||||||
|
assert "CONFIG_ESP_WIFI_ENABLED" not in sdkconfig
|
||||||
|
assert "CONFIG_BT_ENABLED" not in sdkconfig
|
||||||
|
|
||||||
|
|
||||||
|
def test_network_ethernet_only_reconciles_end_to_end(
|
||||||
|
generate_main: Callable[[str | Path], str],
|
||||||
|
component_config_path: Callable[[str], Path],
|
||||||
|
) -> None:
|
||||||
|
"""End-to-end: ethernet's request_ethernet() makes the reconciler disable the
|
||||||
|
WiFi stack and coexistence when WiFi is absent."""
|
||||||
|
generate_main(component_config_path("network_ethernet_only.yaml"))
|
||||||
|
sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
|
||||||
|
assert sdkconfig.get("CONFIG_ESP_WIFI_ENABLED") is False
|
||||||
|
assert sdkconfig.get("CONFIG_SW_COEXIST_ENABLE") is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_network_wifi_ble_coexistence_reconciles_end_to_end(
|
||||||
|
generate_main: Callable[[str | Path], str],
|
||||||
|
component_config_path: Callable[[str], Path],
|
||||||
|
) -> None:
|
||||||
|
"""End-to-end: WiFi + esp32_ble_tracker software_coexistence resolves to
|
||||||
|
BT enabled and coexistence on, with SoftAP/DHCP server dropped (no AP)."""
|
||||||
|
generate_main(component_config_path("network_wifi_ble_coexistence.yaml"))
|
||||||
|
sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
|
||||||
|
assert sdkconfig.get("CONFIG_BT_ENABLED") is True
|
||||||
|
assert sdkconfig.get("CONFIG_BT_BLE_42_FEATURES_SUPPORTED") is True
|
||||||
|
assert sdkconfig.get("CONFIG_SW_COEXIST_ENABLE") is True
|
||||||
|
assert sdkconfig.get("CONFIG_ESP_WIFI_SOFTAP_SUPPORT") is False
|
||||||
|
assert sdkconfig.get("CONFIG_LWIP_DHCPS") is False
|
||||||
|
# WiFi present alongside BT -> WiFi stack must stay enabled.
|
||||||
|
assert "CONFIG_ESP_WIFI_ENABLED" not in sdkconfig
|
||||||
|
|||||||
Reference in New Issue
Block a user