mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 18:06:34 +00:00
Compare commits
42 Commits
release
...
multi-inte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70c6021986 | ||
|
|
1485675928 | ||
|
|
3ed1356bb6 | ||
|
|
d6bc4fea1c | ||
|
|
f818bddac8 | ||
|
|
1f0af903ea | ||
|
|
ed289390df | ||
|
|
8f3010ac64 | ||
|
|
9d9af645ac | ||
|
|
9bfae9e782 | ||
|
|
eb64707d94 | ||
|
|
7814e99b6f | ||
|
|
8ad6813d44 | ||
|
|
1aa0a489f6 | ||
|
|
be3ccd29f6 | ||
|
|
73b8491936 | ||
|
|
1332ebe729 | ||
|
|
028a54422e | ||
|
|
de53e7a6b1 | ||
|
|
0d1d00b654 | ||
|
|
578196ab85 | ||
|
|
d9b712ee5f | ||
|
|
1a61cd622e | ||
|
|
26bdf58daf | ||
|
|
c915a2b8f5 | ||
|
|
7fdb95c2ef | ||
|
|
e44365abca | ||
|
|
532641d523 | ||
|
|
bc36892e7d | ||
|
|
3a02c2f8af | ||
|
|
4a1f9af319 | ||
|
|
9e29bdfdad | ||
|
|
20c975103b | ||
|
|
549b9f85ae | ||
|
|
0fe2310db4 | ||
|
|
5af3e5caef | ||
|
|
47854ff9de | ||
|
|
8a1ddfb1cc | ||
|
|
cde89212fc | ||
|
|
0a518c1e4c | ||
|
|
8c7d2d984e | ||
|
|
8390a98614 |
@@ -17,7 +17,7 @@ from esphome.core import HexInt
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
||||
AUTO_LOAD = ["network"]
|
||||
|
||||
byte_vector = cg.std_vector.template(cg.uint8)
|
||||
peer_address_t = cg.std_ns.class_("array").template(cg.uint8, 6)
|
||||
|
||||
@@ -149,12 +149,6 @@ bool ESPNowComponent::is_wifi_enabled() {
|
||||
}
|
||||
|
||||
void ESPNowComponent::setup() {
|
||||
#ifndef USE_WIFI
|
||||
// Initialize LwIP stack for wake_loop_threadsafe() socket support
|
||||
// When WiFi component is present, it handles esp_netif_init()
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
#endif
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
this->enable_();
|
||||
} else {
|
||||
@@ -174,8 +168,6 @@ void ESPNowComponent::enable() {
|
||||
|
||||
void ESPNowComponent::enable_() {
|
||||
if (!this->is_wifi_enabled()) {
|
||||
esp_event_loop_create_default();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
@@ -3,7 +3,11 @@ import logging
|
||||
|
||||
from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.network import ip_address_literal
|
||||
from esphome.components.network import (
|
||||
KEY_NETWORK_PRIORITY,
|
||||
get_network_priority,
|
||||
ip_address_literal,
|
||||
)
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -13,6 +17,7 @@ from esphome.const import (
|
||||
CONF_DNS1,
|
||||
CONF_DNS2,
|
||||
CONF_DOMAIN,
|
||||
CONF_ENABLE_ON_BOOT,
|
||||
CONF_GATEWAY,
|
||||
CONF_ID,
|
||||
CONF_INTERRUPT_PIN,
|
||||
@@ -27,6 +32,7 @@ from esphome.const import (
|
||||
CONF_PAGE_ID,
|
||||
CONF_PIN,
|
||||
CONF_POLLING_INTERVAL,
|
||||
CONF_PRIORITY,
|
||||
CONF_RESET_PIN,
|
||||
CONF_SPI,
|
||||
CONF_STATIC_IP,
|
||||
@@ -48,7 +54,6 @@ from esphome.core import (
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CONFLICTS_WITH = ["wifi"]
|
||||
AUTO_LOAD = ["network"]
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -348,6 +353,7 @@ BASE_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation(single=True),
|
||||
}
|
||||
@@ -487,6 +493,11 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# Apply network priority if configured, otherwise use the existing default
|
||||
prio = get_network_priority("ethernet")
|
||||
if prio is not None:
|
||||
cg.add(var.set_setup_priority(prio))
|
||||
|
||||
if CORE.is_esp32:
|
||||
await _to_code_esp32(var, config)
|
||||
elif CORE.is_rp2040:
|
||||
@@ -494,6 +505,9 @@ async def to_code(config):
|
||||
|
||||
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
# enable_on_boot defaults to true in C++ - only set if false
|
||||
if not config[CONF_ENABLE_ON_BOOT]:
|
||||
cg.add(var.set_enable_on_boot(False))
|
||||
CORE.data.setdefault(KEY_ETHERNET, {})[ETHERNET_TYPE_KEY] = config[CONF_TYPE]
|
||||
|
||||
if CONF_MANUAL_IP in config:
|
||||
@@ -576,10 +590,16 @@ async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
)
|
||||
cg.add(var.add_phy_register(reg))
|
||||
|
||||
# Disable WiFi when using Ethernet to save memory
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False)
|
||||
# Also disable WiFi/BT coexistence since WiFi is disabled
|
||||
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False)
|
||||
# Disable WiFi when using Ethernet alone to save memory.
|
||||
# When network: priority: lists both interfaces, WiFi must remain enabled.
|
||||
net_priority = CORE.data.get(KEY_NETWORK_PRIORITY, [])
|
||||
priority_ifaces = {e["interface"] for e in net_priority}
|
||||
running_with_wifi = "wifi" in priority_ifaces and "ethernet" in priority_ifaces
|
||||
if not running_with_wifi:
|
||||
# Disable WiFi when using Ethernet to save memory
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False)
|
||||
# Also disable WiFi/BT coexistence since WiFi is disabled
|
||||
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False)
|
||||
|
||||
# Re-enable ESP-IDF's Ethernet driver (excluded by default to save compile time)
|
||||
include_builtin_idf_component("esp_eth")
|
||||
@@ -665,6 +685,17 @@ def _final_validate_rmii_pins(config: ConfigType) -> None:
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
"""Final validation for Ethernet component."""
|
||||
# Allow ethernet + wifi coexistence only when both are declared in network: priority:
|
||||
full = fv.full_config.get()
|
||||
net_priority = full.get("network", {}).get(CONF_PRIORITY, [])
|
||||
priority_ifaces = {e["interface"] for e in net_priority}
|
||||
has_priority_config = "ethernet" in priority_ifaces and "wifi" in priority_ifaces
|
||||
if "wifi" in full and not has_priority_config:
|
||||
raise cv.Invalid(
|
||||
"Component ethernet cannot be used together with component wifi "
|
||||
"unless both are listed under 'network: priority:'"
|
||||
)
|
||||
|
||||
_final_validate_spi(config)
|
||||
_final_validate_rmii_pins(config)
|
||||
return config
|
||||
|
||||
@@ -124,6 +124,17 @@ class EthernetComponent final : public Component {
|
||||
void on_powerdown() override { powerdown(); }
|
||||
bool is_connected() { return this->state_ == EthernetComponentState::CONNECTED; }
|
||||
|
||||
// Per-interface lifecycle (parallels WiFiComponent::enable/disable/is_disabled).
|
||||
// enable_on_boot defaults to true; when false, setup() runs all the driver/netif
|
||||
// installation but skips esp_eth_start(), keeping the link cold until enable() is
|
||||
// called. This is the primary lever for memory reclamation in multi-interface
|
||||
// configurations where only one interface should carry traffic at a time.
|
||||
void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; }
|
||||
void enable();
|
||||
void disable();
|
||||
bool is_disabled() { return this->disabled_; }
|
||||
bool is_enabled() { return !this->disabled_; }
|
||||
|
||||
void set_type(EthernetType type);
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void set_manual_ip(const ManualIP &manual_ip);
|
||||
@@ -194,6 +205,16 @@ class EthernetComponent final : public Component {
|
||||
void finish_connect_();
|
||||
void dump_connect_params_();
|
||||
|
||||
#ifdef USE_ESP32
|
||||
// ESP-IDF only: defers the SPI bus init, netif creation, MAC/PHY install, driver
|
||||
// install, netif attach, and event handler registration (which together allocate
|
||||
// ~3-8KB of DMA-capable internal SRAM via SPI driver state + eth driver RX queue)
|
||||
// until ethernet actually needs to come up. Idempotent — guarded by the
|
||||
// ethernet_initialized_ flag. Called from setup() when enable_on_boot_=true, or
|
||||
// from enable() on first runtime enable. Mirrors wifi_lazy_init_() in WiFi.
|
||||
void ethernet_lazy_init_();
|
||||
#endif
|
||||
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
void notify_ip_state_listeners_();
|
||||
#endif
|
||||
@@ -287,6 +308,17 @@ class EthernetComponent final : public Component {
|
||||
bool started_{false};
|
||||
bool connected_{false};
|
||||
bool got_ipv4_address_{false};
|
||||
// Codegen-time YAML option. When false, setup() defers esp_eth_start().
|
||||
bool enable_on_boot_{true};
|
||||
// Mirror of "is the link intentionally stopped" — set when setup() honors
|
||||
// enable_on_boot=false, cleared by enable(), set again by disable().
|
||||
bool disabled_{false};
|
||||
#ifdef USE_ESP32
|
||||
// Tracks whether ethernet_lazy_init_() has completed successfully. Allows enable()
|
||||
// to be called at runtime after enable_on_boot:false without re-allocating, and
|
||||
// ensures setup() skips the heavy init when enable_on_boot_ is false.
|
||||
bool ethernet_initialized_{false};
|
||||
#endif
|
||||
#if LWIP_IPV6
|
||||
uint8_t ipv6_count_{0};
|
||||
bool ipv6_setup_done_{false};
|
||||
|
||||
@@ -138,6 +138,24 @@ void EthernetComponent::setup() {
|
||||
delay(300); // NOLINT
|
||||
}
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
this->ethernet_lazy_init_();
|
||||
if (!this->ethernet_initialized_) {
|
||||
// lazy_init bailed early via ESPHL_ERROR_CHECK or mark_failed; nothing more to do.
|
||||
return;
|
||||
}
|
||||
esp_err_t err = esp_eth_start(this->eth_handle_);
|
||||
ESPHL_ERROR_CHECK(err, "ETH start error");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "Skipping init (enable_on_boot: false)");
|
||||
this->disabled_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void EthernetComponent::ethernet_lazy_init_() {
|
||||
if (this->ethernet_initialized_)
|
||||
return;
|
||||
|
||||
esp_err_t err;
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
@@ -164,11 +182,7 @@ void EthernetComponent::setup() {
|
||||
err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO);
|
||||
ESPHL_ERROR_CHECK(err, "SPI bus initialize error");
|
||||
#endif
|
||||
|
||||
err = esp_netif_init();
|
||||
ESPHL_ERROR_CHECK(err, "ETH netif init error");
|
||||
err = esp_event_loop_create_default();
|
||||
ESPHL_ERROR_CHECK(err, "ETH event loop error");
|
||||
// Network interface setup handled by network component
|
||||
|
||||
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
this->eth_netif_ = esp_netif_new(&cfg);
|
||||
@@ -375,9 +389,41 @@ void EthernetComponent::setup() {
|
||||
ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error");
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
/* start Ethernet driver state machine */
|
||||
err = esp_eth_start(this->eth_handle_);
|
||||
ESPHL_ERROR_CHECK(err, "ETH start error");
|
||||
this->ethernet_initialized_ = true;
|
||||
}
|
||||
|
||||
void EthernetComponent::enable() {
|
||||
if (!this->disabled_)
|
||||
return;
|
||||
|
||||
ESP_LOGD(TAG, "Enabling");
|
||||
this->ethernet_lazy_init_();
|
||||
if (!this->ethernet_initialized_) {
|
||||
ESP_LOGE(TAG, "Cannot enable - init failed");
|
||||
return;
|
||||
}
|
||||
esp_err_t err = esp_eth_start(this->eth_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_eth_start failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
this->disabled_ = false;
|
||||
// The ETH_EVENT_START handler will set started_=true; the loop state machine
|
||||
// will then drive the STOPPED -> CONNECTING -> CONNECTED transitions.
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void EthernetComponent::disable() {
|
||||
if (this->disabled_)
|
||||
return;
|
||||
|
||||
ESP_LOGD(TAG, "Disabling");
|
||||
esp_err_t err = esp_eth_stop(this->eth_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_eth_stop failed: %s — disabling anyway", esp_err_to_name(err));
|
||||
}
|
||||
this->disabled_ = true;
|
||||
// ETH_EVENT_STOP will clear started_; loop() will transition to STOPPED.
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_config() {
|
||||
@@ -491,6 +537,8 @@ void EthernetComponent::dump_config() {
|
||||
|
||||
network::IPAddresses EthernetComponent::get_ip_addresses() {
|
||||
network::IPAddresses addresses;
|
||||
if (!this->ethernet_initialized_)
|
||||
return addresses; // all-zero IPs
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||
if (err != ESP_OK) {
|
||||
@@ -713,6 +761,10 @@ void EthernetComponent::start_connect_() {
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_connect_params_() {
|
||||
if (!this->ethernet_initialized_) {
|
||||
ESP_LOGCONFIG(TAG, " uninitialized/disabled");
|
||||
return;
|
||||
}
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||
const ip_addr_t *dns_ip1;
|
||||
@@ -780,6 +832,13 @@ void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy
|
||||
#endif
|
||||
|
||||
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
||||
if (!this->ethernet_initialized_) {
|
||||
// External callers (sendspin, ethernet_info, mdns, etc.) may ask for the MAC
|
||||
// before/regardless of whether ethernet is enabled. Fall back to the system MAC
|
||||
// assigned to the ETH interface — same value the driver would have returned.
|
||||
esp_read_mac(mac, ESP_MAC_ETH);
|
||||
return;
|
||||
}
|
||||
esp_err_t err;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac);
|
||||
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
|
||||
@@ -799,6 +858,8 @@ const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer(
|
||||
}
|
||||
|
||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
if (!this->ethernet_initialized_)
|
||||
return ETH_DUPLEX_HALF;
|
||||
esp_err_t err;
|
||||
eth_duplex_t duplex_mode;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
|
||||
@@ -807,6 +868,8 @@ eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
}
|
||||
|
||||
eth_speed_t EthernetComponent::get_link_speed() {
|
||||
if (!this->ethernet_initialized_)
|
||||
return ETH_SPEED_10M;
|
||||
esp_err_t err;
|
||||
eth_speed_t speed;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
|
||||
|
||||
@@ -361,6 +361,23 @@ void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; }
|
||||
void EthernetComponent::set_interrupt_pin(int8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
|
||||
void EthernetComponent::set_reset_pin(int8_t reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
|
||||
void EthernetComponent::enable() {
|
||||
// RP2040 uses arduino-pico's LwipIntfDev which manages link state internally;
|
||||
// there is no clean enable/disable hook today. The YAML option is accepted on
|
||||
// RP2040 for schema parity but has no effect.
|
||||
if (!this->disabled_)
|
||||
return;
|
||||
ESP_LOGW(TAG, "enable_on_boot/disable not supported");
|
||||
this->disabled_ = false;
|
||||
}
|
||||
|
||||
void EthernetComponent::disable() {
|
||||
if (this->disabled_)
|
||||
return;
|
||||
ESP_LOGW(TAG, "enable_on_boot/disable not supported");
|
||||
this->disabled_ = true;
|
||||
}
|
||||
|
||||
} // namespace esphome::ethernet
|
||||
|
||||
#endif // USE_ETHERNET && USE_RP2040
|
||||
|
||||
@@ -5,8 +5,15 @@ import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components.psram import is_guaranteed as psram_is_guaranteed
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENABLE_IPV6, CONF_MIN_IPV6_ADDR_COUNT
|
||||
from esphome.const import (
|
||||
CONF_ENABLE_IPV6,
|
||||
CONF_ID,
|
||||
CONF_MIN_IPV6_ADDR_COUNT,
|
||||
CONF_PRIORITY,
|
||||
CONF_TIMEOUT,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
import esphome.final_validate as fv
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["mdns"]
|
||||
@@ -18,7 +25,30 @@ _LOGGER = logging.getLogger(__name__)
|
||||
KEY_HIGH_PERFORMANCE_NETWORKING = "high_performance_networking"
|
||||
CONF_ENABLE_HIGH_PERFORMANCE = "enable_high_performance"
|
||||
|
||||
# Network priority tracking infrastructure
|
||||
# Components can query this to determine their relative setup priority and fallback timeout.
|
||||
# CORE.data[KEY_NETWORK_PRIORITY] is a list of dicts:
|
||||
# [{"interface": "ethernet", "timeout": 30000}, {"interface": "wifi", "timeout": None}, ...]
|
||||
# where timeout is in milliseconds, or None meaning "start the next interface immediately".
|
||||
KEY_NETWORK_PRIORITY = "network_priority"
|
||||
|
||||
VALID_NETWORK_TYPES = ["ethernet", "openthread", "wifi", "modem"]
|
||||
|
||||
# Setup priority base values — first in list gets the highest priority.
|
||||
#
|
||||
# The base equals the historical setup_priority::WIFI / ::ETHERNET default
|
||||
# (250.0), so a single-entry priority list yields exactly the same setup order
|
||||
# as a config with no priority block. Subsequent entries step down by a small
|
||||
# amount to break ties without crossing other priority bands.
|
||||
#
|
||||
# Important: must stay strictly less than setup_priority::AFTER_BLUETOOTH
|
||||
# (300.0), which NetworkComponent itself uses — otherwise the highest-priority
|
||||
# interface could tie with NetworkComponent and run before esp_netif_init().
|
||||
NETWORK_PRIORITY_BASE = 250.0
|
||||
NETWORK_PRIORITY_STEP = 5.0
|
||||
|
||||
network_ns = cg.esphome_ns.namespace("network")
|
||||
NetworkComponent = network_ns.class_("NetworkComponent", cg.Component)
|
||||
IPAddress = network_ns.class_("IPAddress")
|
||||
|
||||
|
||||
@@ -105,8 +135,160 @@ def has_high_performance_networking() -> bool:
|
||||
return CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False)
|
||||
|
||||
|
||||
def _get_priority_entry(iface: str) -> dict | None:
|
||||
"""Return the priority entry dict for the given interface, or None if not configured."""
|
||||
priority_list = CORE.data.get(KEY_NETWORK_PRIORITY)
|
||||
if priority_list is None:
|
||||
return None
|
||||
iface_lower = iface.lower()
|
||||
for entry in priority_list:
|
||||
if entry["interface"] == iface_lower:
|
||||
return entry
|
||||
return None
|
||||
|
||||
|
||||
def get_network_priority(iface: str) -> float | None:
|
||||
"""Get the setup priority for the given network interface type.
|
||||
|
||||
Returns the float setup priority for ``iface`` based on the order declared
|
||||
under ``network: priority:``. Interfaces listed first receive a higher
|
||||
setup priority so they are initialised before lower-priority ones.
|
||||
|
||||
If no ``network: priority:`` has been configured this returns ``None`` and
|
||||
the calling component should fall back to its own default setup priority.
|
||||
|
||||
Args:
|
||||
iface: Interface type string — one of ``"ethernet"``, ``"wifi"``,
|
||||
``"openthread"`` or ``"modem"`` (case-insensitive).
|
||||
|
||||
Returns:
|
||||
float setup priority, or None if no priority list was configured.
|
||||
|
||||
Example usage inside a component's ``to_code``::
|
||||
|
||||
from esphome.components import network
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
prio = network.get_network_priority("ethernet")
|
||||
if prio is not None:
|
||||
cg.add(var.set_setup_priority(prio))
|
||||
...
|
||||
"""
|
||||
priority_list = CORE.data.get(KEY_NETWORK_PRIORITY)
|
||||
if priority_list is None:
|
||||
return None
|
||||
iface_lower = iface.lower()
|
||||
for idx, entry in enumerate(priority_list):
|
||||
if entry["interface"] == iface_lower:
|
||||
return NETWORK_PRIORITY_BASE - (idx * NETWORK_PRIORITY_STEP)
|
||||
return None
|
||||
|
||||
|
||||
def get_network_timeout(iface: str) -> int | None:
|
||||
"""Get the fallback timeout in milliseconds for the given network interface.
|
||||
|
||||
Returns the timeout (in ms) that the runtime should wait for ``iface`` to
|
||||
connect before attempting to bring up the next interface in the priority
|
||||
list. Returns ``None`` if no timeout was configured for this interface,
|
||||
meaning the next interface should start immediately.
|
||||
|
||||
Args:
|
||||
iface: Interface type string — one of ``"ethernet"``, ``"wifi"``,
|
||||
``"openthread"`` or ``"modem"`` (case-insensitive).
|
||||
|
||||
Returns:
|
||||
int timeout in milliseconds, or None if no timeout is configured.
|
||||
|
||||
Example usage inside a component's ``to_code``::
|
||||
|
||||
from esphome.components import network
|
||||
|
||||
async def to_code(config):
|
||||
...
|
||||
timeout_ms = network.get_network_timeout("ethernet")
|
||||
if timeout_ms is not None:
|
||||
cg.add(var.set_fallback_timeout(timeout_ms))
|
||||
...
|
||||
"""
|
||||
entry = _get_priority_entry(iface)
|
||||
if entry is None:
|
||||
return None
|
||||
return entry.get("timeout")
|
||||
|
||||
|
||||
def _validate_timeout(value):
|
||||
"""Accept any common ESPHome/HA time period format, or a plain integer as seconds.
|
||||
|
||||
Accepted formats: 30s, 10sec, 1min, 500ms, 1h, 1.5h, 30 (plain int → 30s).
|
||||
"""
|
||||
if isinstance(value, int):
|
||||
# Plain integer — treat as seconds, e.g. timeout: 30 means 30s
|
||||
return cv.positive_time_period_milliseconds(f"{value}s")
|
||||
return cv.positive_time_period_milliseconds(value)
|
||||
|
||||
|
||||
def _priority_entry_schema(value):
|
||||
"""Validate a single priority list entry in either plain string or mapping form.
|
||||
|
||||
Plain string form (no timeout):
|
||||
- ethernet
|
||||
|
||||
Mapping form with optional timeout:
|
||||
- ethernet:
|
||||
timeout: 30s
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return cv.one_of(*VALID_NETWORK_TYPES, lower=True)(value)
|
||||
if isinstance(value, dict):
|
||||
if len(value) != 1:
|
||||
raise cv.Invalid(
|
||||
"Each priority entry must have exactly one interface name as its key"
|
||||
)
|
||||
iface = next(iter(value))
|
||||
cv.one_of(*VALID_NETWORK_TYPES, lower=True)(iface)
|
||||
opts = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TIMEOUT): _validate_timeout,
|
||||
}
|
||||
)(value[iface] or {})
|
||||
return {iface: opts}
|
||||
raise cv.Invalid(
|
||||
f"Expected an interface name string or a mapping, got {type(value).__name__}"
|
||||
)
|
||||
|
||||
|
||||
def _normalize_priority_entry(value) -> dict:
|
||||
"""Normalize a validated priority entry to a canonical dict.
|
||||
|
||||
Returns a dict with keys:
|
||||
- ``interface``: str, lowercase interface name
|
||||
- ``timeout``: int milliseconds, or None
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return {"interface": value, "timeout": None}
|
||||
# Mapping form — exactly one key (the interface name)
|
||||
iface, opts = next(iter(value.items()))
|
||||
timeout = opts.get(CONF_TIMEOUT)
|
||||
timeout_ms = int(timeout.total_milliseconds) if timeout is not None else None
|
||||
return {"interface": iface, "timeout": timeout_ms}
|
||||
|
||||
|
||||
def _validate_priority_list(value):
|
||||
"""Validate and normalize the full priority list, rejecting duplicates."""
|
||||
raw = cv.ensure_list(_priority_entry_schema)(value)
|
||||
entries = [_normalize_priority_entry(e) for e in raw]
|
||||
interfaces = [e["interface"] for e in entries]
|
||||
if len(interfaces) != len(set(interfaces)):
|
||||
raise cv.Invalid("Duplicate entries are not allowed in 'priority'")
|
||||
return entries
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NetworkComponent),
|
||||
cv.SplitDefault(
|
||||
CONF_ENABLE_IPV6,
|
||||
bk72xx=False,
|
||||
@@ -130,15 +312,53 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
),
|
||||
cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_ENABLE_HIGH_PERFORMANCE): cv.All(cv.boolean, cv.only_on_esp32),
|
||||
cv.Optional(CONF_PRIORITY): _validate_priority_list,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
"""Check that every interface named in 'priority' has a corresponding component block."""
|
||||
full = fv.full_config.get()
|
||||
for entry in config.get(CONF_PRIORITY, []):
|
||||
iface = entry["interface"]
|
||||
if iface not in full:
|
||||
raise cv.Invalid(
|
||||
f"'{iface}' is listed in 'network: priority:' but no '{iface}:' "
|
||||
f"component is configured",
|
||||
[CONF_PRIORITY],
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.NETWORK)
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_NETWORK")
|
||||
# ESP32 with Arduino uses ESP-IDF network APIs directly, no Arduino Network library needed
|
||||
|
||||
# Store the user-declared network priority list in CORE.data so that ethernet,
|
||||
# wifi and other network components can query it via get_network_priority() and
|
||||
# get_network_timeout() during their own to_code phase.
|
||||
if CONF_PRIORITY in config:
|
||||
priority_list = config[CONF_PRIORITY]
|
||||
CORE.data[KEY_NETWORK_PRIORITY] = priority_list
|
||||
# Enable Component::set_setup_priority() so the per-interface to_code
|
||||
# calls below have a defined symbol to link against. Without this define
|
||||
# the implementation in core/component.cpp is compiled out.
|
||||
cg.add_define("USE_SETUP_PRIORITY_OVERRIDE")
|
||||
|
||||
def _fmt(entry):
|
||||
if entry["timeout"] is not None:
|
||||
return f"{entry['interface']} (timeout: {entry['timeout']}ms)"
|
||||
return entry["interface"]
|
||||
|
||||
_LOGGER.info(
|
||||
"Network interface priority: %s",
|
||||
" > ".join(_fmt(e) for e in priority_list),
|
||||
)
|
||||
|
||||
# Apply high performance networking settings
|
||||
# Config can explicitly enable/disable, or default to component-driven behavior
|
||||
enable_high_perf = config.get(CONF_ENABLE_HIGH_PERFORMANCE)
|
||||
@@ -224,3 +444,22 @@ async def to_code(config):
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
|
||||
if CORE.is_rp2040:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
|
||||
# Pvariable creation lives in a separate coroutine at NETWORK_SERVICES so it
|
||||
# emits after wifi/ethernet at COMMUNICATION. This keeps compile-time config
|
||||
# (above) separate from C++ object lifecycle and allows wiring in interface
|
||||
# pointers via get_variable().
|
||||
if CORE.is_esp32:
|
||||
CORE.add_job(network_component_to_code, config)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.NETWORK_SERVICES)
|
||||
async def network_component_to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# Pass the priority list to the C++ component. NetworkComponent::add_priority_entry
|
||||
# captures the interface-name string literal pointer; CORE.data[KEY_NETWORK_PRIORITY]
|
||||
# holds the normalized list of dicts (`{"interface": str, "timeout": int|None}`).
|
||||
for entry in CORE.data.get(KEY_NETWORK_PRIORITY, []):
|
||||
timeout_ms = entry["timeout"] if entry["timeout"] is not None else 0
|
||||
cg.add(var.add_priority_entry(entry["interface"], timeout_ms))
|
||||
|
||||
119
esphome/components/network/network_component.cpp
Normal file
119
esphome/components/network/network_component.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "network_component.h"
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#if defined(USE_NETWORK) && defined(USE_ESP32)
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
|
||||
namespace esphome::network {
|
||||
|
||||
static const char *const TAG = "network";
|
||||
|
||||
void NetworkComponent::setup() {
|
||||
// Initialize ESP-IDF network interfaces and ensure the default event loop exists
|
||||
esp_err_t err;
|
||||
err = esp_netif_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_netif_init failed: (%d) %s", err, esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
err = esp_event_loop_create_default();
|
||||
// ESP_ERR_INVALID_STATE is returned if the default loop already exists,
|
||||
// which is fine since we just want to make sure it exists
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "esp_event_loop_create_default failed: (%d) %s", err, esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Register an IP_EVENT handler so we can re-arbitrate the default netif on every
|
||||
// interface up/down. ESP-IDF's built-in auto-selection picks by route_prio (WiFi STA = 100
|
||||
// > Ethernet = 50), which inverts the user's stated priority for same-subnet configurations.
|
||||
// We register AFTER esp-idf's internal handler, so our esp_netif_set_default_netif() call
|
||||
// wins and stays sticky thanks to esp-idf's "manual override" flag.
|
||||
err = esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &NetworkComponent::event_handler_, this);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "IP_EVENT handler register failed: %s — default route arbitration disabled",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Defensive: arbitrate now in case an interface came up before our handler registered
|
||||
// (unlikely given our AFTER_BLUETOOTH priority but cheap).
|
||||
this->update_default_netif_();
|
||||
}
|
||||
|
||||
void NetworkComponent::add_priority_entry(const char *interface, uint32_t timeout_ms) {
|
||||
if (this->priority_list_.size() >= MAX_NETWORK_PRIORITY_ENTRIES) {
|
||||
ESP_LOGW(TAG, "Priority list full; ignoring '%s'", interface);
|
||||
return;
|
||||
}
|
||||
this->priority_list_.push_back({interface, timeout_ms});
|
||||
}
|
||||
|
||||
const char *NetworkComponent::interface_to_ifkey_(const char *interface) {
|
||||
// Standard ESP-IDF netif keys. esphome's wifi/ethernet/openthread components create
|
||||
// netifs using these defaults.
|
||||
if (std::strcmp(interface, "ethernet") == 0)
|
||||
return "ETH_DEF";
|
||||
if (std::strcmp(interface, "wifi") == 0)
|
||||
return "WIFI_STA_DEF"; // STA carries uplink; AP never wins default route
|
||||
if (std::strcmp(interface, "openthread") == 0)
|
||||
return "OT_DEF";
|
||||
if (std::strcmp(interface, "modem") == 0)
|
||||
return "PPP_DEF";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NetworkComponent::event_handler_(void *arg, esp_event_base_t /*base*/, int32_t /*id*/, void * /*data*/) {
|
||||
auto *self = static_cast<NetworkComponent *>(arg);
|
||||
self->update_default_netif_();
|
||||
}
|
||||
|
||||
void NetworkComponent::update_default_netif_() {
|
||||
// No priority list configured → leave ESP-IDF's route_prio-based auto-selection alone.
|
||||
// Single-interface configs behave exactly as before.
|
||||
if (this->priority_list_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &entry : this->priority_list_) {
|
||||
const char *ifkey = interface_to_ifkey_(entry.interface);
|
||||
if (ifkey == nullptr)
|
||||
continue;
|
||||
|
||||
esp_netif_t *netif = esp_netif_get_handle_from_ifkey(ifkey);
|
||||
if (netif == nullptr)
|
||||
continue; // component for this interface hasn't run setup() yet
|
||||
|
||||
// is_netif_up returns true only when the netif has link + IP, which is what
|
||||
// we want for "this interface can carry traffic right now."
|
||||
if (!esp_netif_is_netif_up(netif))
|
||||
continue;
|
||||
|
||||
if (netif != this->active_netif_) {
|
||||
ESP_LOGI(TAG, "Default interface: %s", entry.interface);
|
||||
esp_netif_set_default_netif(netif);
|
||||
this->active_interface_ = entry.interface;
|
||||
this->active_netif_ = netif;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// No priority-listed interface is currently up.
|
||||
if (this->active_netif_ != nullptr) {
|
||||
ESP_LOGD(TAG, "No active interface in priority list");
|
||||
this->active_interface_ = nullptr;
|
||||
this->active_netif_ = nullptr;
|
||||
// We intentionally don't clear esp-idf's default — the next interface that comes
|
||||
// up will trigger our handler again and we'll re-pick.
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::network
|
||||
#endif
|
||||
54
esphome/components/network/network_component.h
Normal file
54
esphome/components/network/network_component.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#if defined(USE_NETWORK) && defined(USE_ESP32)
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
|
||||
namespace esphome::network {
|
||||
|
||||
// Cap matches the number of interface types the priority list accepts in YAML
|
||||
// (ethernet, wifi, openthread, modem). StaticVector keeps zero heap allocation.
|
||||
inline constexpr size_t MAX_NETWORK_PRIORITY_ENTRIES = 4;
|
||||
|
||||
struct NetworkPriorityEntry {
|
||||
const char *interface; // YAML name: "ethernet", "wifi", "openthread", "modem"
|
||||
uint32_t timeout_ms; // 0 = no timeout; consumed by Unit D (runtime fallback)
|
||||
};
|
||||
|
||||
class NetworkComponent : public Component {
|
||||
public:
|
||||
void setup() 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; }
|
||||
|
||||
// Codegen-time priority list construction. Called once per `network: priority:` entry
|
||||
// in YAML order. The interface name pointer must have static storage duration.
|
||||
void add_priority_entry(const char *interface, uint32_t timeout_ms);
|
||||
|
||||
// Currently-active interface in priority order (the one set as default netif).
|
||||
// Returns nullptr if no priority list is configured or no interface is up.
|
||||
const char *get_active_interface() const { return this->active_interface_; }
|
||||
esp_netif_t *get_active_netif() const { return this->active_netif_; }
|
||||
|
||||
protected:
|
||||
// Maps a YAML interface name to its ESP-IDF netif if-key.
|
||||
// Returns nullptr if the interface name is not recognized.
|
||||
static const char *interface_to_ifkey_(const char *interface);
|
||||
|
||||
// ESP-IDF event handler trampoline. Fires on IP_EVENT_* and re-arbitrates the default netif.
|
||||
static void event_handler_(void *arg, esp_event_base_t base, int32_t id, void *data);
|
||||
|
||||
// Walk priority_list_ in order. Set the highest-priority netif that is up as the
|
||||
// ESP-IDF default. No-op if priority_list_ is empty (single-interface configs).
|
||||
void update_default_netif_();
|
||||
|
||||
StaticVector<NetworkPriorityEntry, MAX_NETWORK_PRIORITY_ENTRIES> priority_list_;
|
||||
const char *active_interface_{nullptr};
|
||||
esp_netif_t *active_netif_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace esphome::network
|
||||
#endif
|
||||
@@ -35,9 +35,8 @@ void OpenThreadComponent::setup() {
|
||||
esp_vfs_eventfd_config_t eventfd_config = {
|
||||
.max_fds = 3,
|
||||
};
|
||||
// Network interface setup handled by network component
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
|
||||
|
||||
xTaskCreate(
|
||||
|
||||
@@ -12,6 +12,7 @@ from esphome.components.esp32 import (
|
||||
only_on_variant,
|
||||
)
|
||||
from esphome.components.network import (
|
||||
get_network_priority,
|
||||
has_high_performance_networking,
|
||||
ip_address_literal,
|
||||
)
|
||||
@@ -42,7 +43,6 @@ from esphome.const import (
|
||||
CONF_ON_CONNECT,
|
||||
CONF_ON_DISCONNECT,
|
||||
CONF_ON_ERROR,
|
||||
CONF_OUTPUT_POWER,
|
||||
CONF_PASSWORD,
|
||||
CONF_POWER_SAVE_MODE,
|
||||
CONF_PRIORITY,
|
||||
@@ -440,6 +440,7 @@ def _validate(config):
|
||||
return config
|
||||
|
||||
|
||||
CONF_OUTPUT_POWER = "output_power"
|
||||
CONF_PASSIVE_SCAN = "passive_scan"
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
@@ -573,6 +574,10 @@ def wifi_network(config, ap, static_ip):
|
||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
prio = get_network_priority("wifi")
|
||||
if prio is not None:
|
||||
cg.add(var.set_setup_priority(prio))
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
|
||||
# Track if any network uses Enterprise authentication
|
||||
|
||||
@@ -632,11 +632,11 @@ void WiFiComponent::setup() {
|
||||
#endif
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
#ifdef USE_ESP32
|
||||
this->wifi_lazy_init_();
|
||||
#endif
|
||||
this->start();
|
||||
} else {
|
||||
#ifdef USE_ESP32
|
||||
esp_netif_init();
|
||||
#endif
|
||||
this->state_ = WIFI_COMPONENT_STATE_DISABLED;
|
||||
}
|
||||
}
|
||||
@@ -1278,6 +1278,11 @@ void WiFiComponent::enable() {
|
||||
|
||||
ESP_LOGD(TAG, "Enabling");
|
||||
this->state_ = WIFI_COMPONENT_STATE_OFF;
|
||||
#ifdef USE_ESP32
|
||||
// Idempotent — only allocates DMA buffers + netifs on the first call. After this,
|
||||
// start() can safely run.
|
||||
this->wifi_lazy_init_();
|
||||
#endif
|
||||
this->start();
|
||||
}
|
||||
|
||||
|
||||
@@ -694,6 +694,12 @@ class WiFiComponent final : public Component {
|
||||
bool wifi_apply_hostname_();
|
||||
bool wifi_sta_connect_(const WiFiAP &ap);
|
||||
void wifi_pre_setup_();
|
||||
#ifdef USE_ESP32
|
||||
// ESP-IDF only: defers esp_wifi_init() + netif creation (which allocate ~15-30KB of
|
||||
// DMA-capable internal SRAM) until wifi actually needs to come up. Idempotent.
|
||||
// Called from setup() only when enable_on_boot_=true, and from enable() on first use.
|
||||
void wifi_lazy_init_();
|
||||
#endif
|
||||
WiFiSTAConnectStatus wifi_sta_connect_status_() const;
|
||||
bool is_connected_() const {
|
||||
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED &&
|
||||
@@ -889,6 +895,12 @@ class WiFiComponent final : public Component {
|
||||
bool rrm_{false};
|
||||
#endif
|
||||
bool enable_on_boot_{true};
|
||||
#ifdef USE_ESP32
|
||||
// Tracks whether esp_wifi_init() + netif creation has happened. Allows enable()
|
||||
// to be called at runtime without re-allocating, and ensures the heavy init is
|
||||
// skipped entirely when enable_on_boot_ is false until first enable().
|
||||
bool wifi_initialized_{false};
|
||||
#endif
|
||||
bool got_ipv4_address_{false};
|
||||
bool keep_scan_results_{false};
|
||||
bool has_completed_scan_after_captive_portal_start_{
|
||||
|
||||
@@ -145,23 +145,15 @@ void WiFiComponent::wifi_pre_setup_() {
|
||||
get_mac_address_raw(mac);
|
||||
set_mac_address(mac);
|
||||
}
|
||||
esp_err_t err = esp_netif_init();
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
// Network interface setup handled by network component
|
||||
s_wifi_event_group = xEventGroupCreate();
|
||||
if (s_wifi_event_group == nullptr) {
|
||||
ESP_LOGE(TAG, "xEventGroupCreate failed");
|
||||
return;
|
||||
}
|
||||
err = esp_event_loop_create_default();
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGE(TAG, "esp_event_loop_create_default failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
esp_event_handler_instance_t instance_wifi_id, instance_ip_id;
|
||||
err = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, nullptr, &instance_wifi_id);
|
||||
esp_err_t err =
|
||||
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, nullptr, &instance_wifi_id);
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGE(TAG, "esp_event_handler_instance_register failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
@@ -171,6 +163,16 @@ void WiFiComponent::wifi_pre_setup_() {
|
||||
ESP_LOGE(TAG, "esp_event_handler_instance_register failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
// NOTE: netif creation + esp_wifi_init() used to live here. They allocate ~15-30KB of
|
||||
// DMA-capable internal SRAM, which competes with W5500 SPI DMA and I2S DMA on
|
||||
// memory-tight devices. They are now deferred to wifi_lazy_init_(), called from
|
||||
// setup() when enable_on_boot_ is true, or from enable() on first runtime enable.
|
||||
// This makes enable_on_boot:false genuinely skip the wifi DMA allocation.
|
||||
}
|
||||
|
||||
void WiFiComponent::wifi_lazy_init_() {
|
||||
if (this->wifi_initialized_)
|
||||
return;
|
||||
|
||||
s_sta_netif = esp_netif_create_default_wifi_sta();
|
||||
|
||||
@@ -183,7 +185,7 @@ void WiFiComponent::wifi_pre_setup_() {
|
||||
ESP_LOGW(TAG, "starting wifi without nvs");
|
||||
cfg.nvs_enable = false;
|
||||
}
|
||||
err = esp_wifi_init(&cfg);
|
||||
esp_err_t err = esp_wifi_init(&cfg);
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGE(TAG, "esp_wifi_init failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
@@ -193,6 +195,7 @@ void WiFiComponent::wifi_pre_setup_() {
|
||||
ESP_LOGE(TAG, "esp_wifi_set_storage failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
this->wifi_initialized_ = true;
|
||||
}
|
||||
|
||||
bool WiFiComponent::wifi_mode_(optional<bool> sta, optional<bool> ap) {
|
||||
|
||||
Reference in New Issue
Block a user