[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:
Keith Burzinski
2026-06-17 22:09:39 -05:00
committed by GitHub
parent 3a1a8a8955
commit c2784c9fd8
12 changed files with 382 additions and 28 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
wifi:
ssid: "test_ssid"
password: "test_password"

View File

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