mirror of
https://github.com/esphome/esphome.git
synced 2025-11-12 04:45:47 +00:00
Compare commits
24 Commits
ethernet_m
...
beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d71b6b93e | ||
|
|
298813d4fa | ||
|
|
56d141c741 | ||
|
|
47a7f729dd | ||
|
|
7806eb980f | ||
|
|
a59888224c | ||
|
|
58ad4759f0 | ||
|
|
87f79290ba | ||
|
|
9326d78439 | ||
|
|
a93887a790 | ||
|
|
d7fa131a8a | ||
|
|
79a4444928 | ||
|
|
572fae5c7d | ||
|
|
5dafaaced4 | ||
|
|
65a303d48f | ||
|
|
00c71b7236 | ||
|
|
ef04903a7a | ||
|
|
a2ec7f622c | ||
|
|
2f91e7bd47 | ||
|
|
80a7c6d3c3 | ||
|
|
7a92565a0c | ||
|
|
661920c51e | ||
|
|
a6b905e148 | ||
|
|
a6b7c1f18c |
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.11.0-dev
|
||||
PROJECT_NUMBER = 2025.11.0b1
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -49,9 +49,9 @@ void DebugComponent::dump_config() {
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
|
||||
#ifdef USE_ESP32
|
||||
this->log_partition_info_(); // Log partition information for ESP32
|
||||
#endif // USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ZEPHYR)
|
||||
this->log_partition_info_(); // Log partition information
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugComponent::loop() {
|
||||
|
||||
@@ -62,19 +62,19 @@ class DebugComponent : public PollingComponent {
|
||||
sensor::Sensor *cpu_frequency_sensor_{nullptr};
|
||||
#endif // USE_SENSOR
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ZEPHYR)
|
||||
/**
|
||||
* @brief Logs information about the device's partition table.
|
||||
*
|
||||
* This function iterates through the ESP32's partition table and logs details
|
||||
* This function iterates through the partition table and logs details
|
||||
* about each partition, including its name, type, subtype, starting address,
|
||||
* and size. The information is useful for diagnosing issues related to flash
|
||||
* memory or verifying the partition configuration dynamically at runtime.
|
||||
*
|
||||
* Only available when compiled for ESP32 platforms.
|
||||
* Only available when compiled for ESP32 and ZEPHYR platforms.
|
||||
*/
|
||||
void log_partition_info_();
|
||||
#endif // USE_ESP32
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *device_info_{nullptr};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <zephyr/drivers/hwinfo.h>
|
||||
#include <hal/nrf_power.h>
|
||||
#include <cstdint>
|
||||
#include <zephyr/storage/flash_map.h>
|
||||
|
||||
#define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0]
|
||||
|
||||
@@ -86,6 +87,37 @@ std::string DebugComponent::get_reset_reason_() {
|
||||
|
||||
uint32_t DebugComponent::get_free_heap_() { return INT_MAX; }
|
||||
|
||||
static void fa_cb(const struct flash_area *fa, void *user_data) {
|
||||
#if CONFIG_FLASH_MAP_LABELS
|
||||
const char *fa_label = flash_area_label(fa);
|
||||
|
||||
if (fa_label == nullptr) {
|
||||
fa_label = "-";
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "%2d 0x%0*" PRIxPTR " %-26s %-24.24s 0x%-10x 0x%-12x", (int) fa->fa_id,
|
||||
sizeof(uintptr_t) * 2, (uintptr_t) fa->fa_dev, fa->fa_dev->name, fa_label, (uint32_t) fa->fa_off,
|
||||
fa->fa_size);
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, "%2d 0x%0*" PRIxPTR " %-26s 0x%-10x 0x%-12x", (int) fa->fa_id, sizeof(uintptr_t) * 2,
|
||||
(uintptr_t) fa->fa_dev, fa->fa_dev->name, (uint32_t) fa->fa_off, fa->fa_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugComponent::log_partition_info_() {
|
||||
#if CONFIG_FLASH_MAP_LABELS
|
||||
ESP_LOGCONFIG(TAG, "ID | Device | Device Name "
|
||||
"| Label | Offset | Size");
|
||||
ESP_LOGCONFIG(TAG, "--------------------------------------------"
|
||||
"-----------------------------------------------");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, "ID | Device | Device Name "
|
||||
"| Offset | Size");
|
||||
ESP_LOGCONFIG(TAG, "-----------------------------------------"
|
||||
"------------------------------");
|
||||
#endif
|
||||
flash_area_foreach(fa_cb, nullptr);
|
||||
}
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
std::string supply = "Main supply status: ";
|
||||
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) {
|
||||
|
||||
@@ -23,7 +23,7 @@ void DS1307Component::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float DS1307Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -336,7 +336,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
|
||||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
wifi::global_wifi_component->start_connecting(sta);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
@@ -383,7 +383,6 @@ async def to_code(config):
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
|
||||
if CONF_MANUAL_IP in config:
|
||||
cg.add_define("USE_ETHERNET_MANUAL_IP")
|
||||
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
|
||||
|
||||
# Add compile-time define for PHY types with specific code
|
||||
|
||||
@@ -550,14 +550,11 @@ void EthernetComponent::start_connect_() {
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t info;
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
info.ip = this->manual_ip_->static_ip;
|
||||
info.gw = this->manual_ip_->gateway;
|
||||
info.netmask = this->manual_ip_->subnet;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
} else {
|
||||
info.ip.addr = 0;
|
||||
info.gw.addr = 0;
|
||||
info.netmask.addr = 0;
|
||||
@@ -578,7 +575,6 @@ void EthernetComponent::start_connect_() {
|
||||
err = esp_netif_set_ip_info(this->eth_netif_, &info);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
|
||||
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
LwIPLock lock;
|
||||
if (this->manual_ip_->dns1.is_set()) {
|
||||
@@ -591,9 +587,7 @@ void EthernetComponent::start_connect_() {
|
||||
d = this->manual_ip_->dns2;
|
||||
dns_setserver(1, &d);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
} else {
|
||||
err = esp_netif_dhcpc_start(this->eth_netif_);
|
||||
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC start error");
|
||||
@@ -691,9 +685,7 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->cl
|
||||
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
|
||||
#endif
|
||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
#endif
|
||||
|
||||
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
||||
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
||||
|
||||
@@ -82,9 +82,7 @@ class EthernetComponent : public Component {
|
||||
void add_phy_register(PHYRegister register_value);
|
||||
#endif
|
||||
void set_type(EthernetType type);
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void set_manual_ip(const ManualIP &manual_ip);
|
||||
#endif
|
||||
void set_fixed_mac(const std::array<uint8_t, 6> &mac) { this->fixed_mac_ = mac; }
|
||||
|
||||
network::IPAddresses get_ip_addresses();
|
||||
@@ -139,9 +137,7 @@ class EthernetComponent : public Component {
|
||||
uint8_t mdc_pin_{23};
|
||||
uint8_t mdio_pin_{18};
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
optional<ManualIP> manual_ip_{};
|
||||
#endif
|
||||
uint32_t connect_begin_;
|
||||
|
||||
// Group all uint8_t types together (enums and bools)
|
||||
|
||||
@@ -7,10 +7,8 @@ namespace homeassistant {
|
||||
static const char *const TAG = "homeassistant.time";
|
||||
|
||||
void HomeassistantTime::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Home Assistant Time:\n"
|
||||
" Timezone: '%s'",
|
||||
this->timezone_.c_str());
|
||||
ESP_LOGCONFIG(TAG, "Home Assistant Time");
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -231,7 +231,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
wifi::global_wifi_component->start_connecting(sta);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
@@ -331,7 +331,7 @@ async def to_code(configs):
|
||||
# This must be done after all widgets are created
|
||||
for comp in helpers.lvgl_components_required:
|
||||
cg.add_define(f"USE_LVGL_{comp.upper()}")
|
||||
if "transform_angle" in styles_used:
|
||||
if {"transform_angle", "transform_zoom"} & styles_used:
|
||||
add_define("LV_COLOR_SCREEN_TRANSP", "1")
|
||||
for use in helpers.lv_uses:
|
||||
add_define(f"LV_USE_{use.upper()}")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
|
||||
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.core import CORE, CoroPriority, coroutine_with_priority
|
||||
@@ -9,6 +11,13 @@ from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["mdns"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# High performance networking tracking infrastructure
|
||||
# Components can request high performance networking and this configures lwip and WiFi settings
|
||||
KEY_HIGH_PERFORMANCE_NETWORKING = "high_performance_networking"
|
||||
CONF_ENABLE_HIGH_PERFORMANCE = "enable_high_performance"
|
||||
|
||||
network_ns = cg.esphome_ns.namespace("network")
|
||||
IPAddress = network_ns.class_("IPAddress")
|
||||
|
||||
@@ -47,6 +56,55 @@ def ip_address_literal(ip: str | int | None) -> cg.MockObj:
|
||||
return IPAddress(str(ip))
|
||||
|
||||
|
||||
def require_high_performance_networking() -> None:
|
||||
"""Request high performance networking for network and WiFi.
|
||||
|
||||
Call this from components that need optimized network performance for streaming
|
||||
or high-throughput data transfer. This enables high performance mode which
|
||||
configures both lwip TCP settings and WiFi driver settings for improved
|
||||
network performance.
|
||||
|
||||
Settings applied (ESP-IDF only):
|
||||
- lwip: Larger TCP buffers, windows, and mailbox sizes
|
||||
- WiFi: Increased RX/TX buffers, AMPDU aggregation, PSRAM allocation (set by wifi component)
|
||||
|
||||
Configuration is PSRAM-aware:
|
||||
- With PSRAM guaranteed: Aggressive settings (512 RX buffers, 512KB TCP windows)
|
||||
- Without PSRAM: Conservative optimized settings (64 buffers, 65KB TCP windows)
|
||||
|
||||
Example:
|
||||
from esphome.components import network
|
||||
|
||||
def _request_high_performance_networking(config):
|
||||
network.require_high_performance_networking()
|
||||
return config
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
...,
|
||||
_request_high_performance_networking,
|
||||
)
|
||||
"""
|
||||
# Only set up once (idempotent - multiple components can call this)
|
||||
if not CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False):
|
||||
CORE.data[KEY_HIGH_PERFORMANCE_NETWORKING] = True
|
||||
|
||||
|
||||
def has_high_performance_networking() -> bool:
|
||||
"""Check if high performance networking mode is enabled.
|
||||
|
||||
Returns True when high performance networking has been requested by a
|
||||
component or explicitly enabled in the network configuration. This indicates
|
||||
that lwip and WiFi will use optimized buffer sizes and settings.
|
||||
|
||||
This function should be called during code generation (to_code phase) by
|
||||
components that need to apply performance-related settings.
|
||||
|
||||
Returns:
|
||||
bool: True if high performance networking is enabled, False otherwise
|
||||
"""
|
||||
return CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.SplitDefault(
|
||||
@@ -71,6 +129,7 @@ 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),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -80,6 +139,70 @@ async def to_code(config):
|
||||
cg.add_define("USE_NETWORK")
|
||||
if CORE.using_arduino and CORE.is_esp32:
|
||||
cg.add_library("Networking", None)
|
||||
|
||||
# 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)
|
||||
component_requested = CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False)
|
||||
|
||||
# Explicit config overrides component request
|
||||
should_enable = (
|
||||
enable_high_perf if enable_high_perf is not None else component_requested
|
||||
)
|
||||
|
||||
# Log when user explicitly disables but a component requested it
|
||||
if enable_high_perf is False and component_requested:
|
||||
_LOGGER.info(
|
||||
"High performance networking disabled by user configuration (overriding component request)"
|
||||
)
|
||||
|
||||
if CORE.is_esp32 and CORE.using_esp_idf and should_enable:
|
||||
# Check if PSRAM is guaranteed (set by psram component during final validation)
|
||||
psram_guaranteed = psram_is_guaranteed()
|
||||
|
||||
if psram_guaranteed:
|
||||
_LOGGER.info(
|
||||
"Applying high-performance lwip settings (PSRAM guaranteed): 512KB TCP windows, 512 mailbox sizes"
|
||||
)
|
||||
# PSRAM is guaranteed - use aggressive settings
|
||||
# Higher maximum values are allowed because CONFIG_LWIP_WND_SCALE is set to true
|
||||
# CONFIG_LWIP_WND_SCALE can only be enabled if CONFIG_SPIRAM_IGNORE_NOTFOUND isn't set
|
||||
# Based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702
|
||||
|
||||
# Enable window scaling for much larger TCP windows
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_WND_SCALE", True)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RCV_SCALE", 3)
|
||||
|
||||
# Large TCP buffers and windows (requires PSRAM)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 512000)
|
||||
|
||||
# Large mailboxes for high throughput
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 512)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 512)
|
||||
|
||||
# TCP connection limits
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_ACTIVE_TCP", 16)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_LISTENING_TCP", 16)
|
||||
|
||||
# TCP optimizations
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MAXRTX", 12)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SYNMAXRTX", 6)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MSS", 1436)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MSL", 60000)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_OVERSIZE_MSS", True)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_QUEUE_OOSEQ", True)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Applying optimized lwip settings: 65KB TCP windows, 64 mailbox sizes"
|
||||
)
|
||||
# PSRAM not guaranteed - use more conservative, but still optimized settings
|
||||
# Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 65534)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64)
|
||||
|
||||
if (enable_ipv6 := config.get(CONF_ENABLE_IPV6, None)) is not None:
|
||||
cg.add_define("USE_NETWORK_IPV6", enable_ipv6)
|
||||
if enable_ipv6:
|
||||
|
||||
@@ -25,6 +25,7 @@ from esphome.const import (
|
||||
CONF_FRAMEWORK,
|
||||
CONF_ID,
|
||||
CONF_RESET_PIN,
|
||||
CONF_VOLTAGE,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
@@ -102,6 +103,10 @@ nrf52_ns = cg.esphome_ns.namespace("nrf52")
|
||||
DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component)
|
||||
|
||||
CONF_DFU = "dfu"
|
||||
CONF_REG0 = "reg0"
|
||||
CONF_UICR_ERASE = "uicr_erase"
|
||||
|
||||
VOLTAGE_LEVELS = [1.8, 2.1, 2.4, 2.7, 3.0, 3.3]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
_detect_bootloader,
|
||||
@@ -116,6 +121,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_REG0): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VOLTAGE): cv.All(
|
||||
cv.voltage,
|
||||
cv.one_of(*VOLTAGE_LEVELS, float=True),
|
||||
),
|
||||
cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -183,6 +197,12 @@ async def to_code(config: ConfigType) -> None:
|
||||
if dfu_config := config.get(CONF_DFU):
|
||||
CORE.add_job(_dfu_to_code, dfu_config)
|
||||
|
||||
if reg0_config := config.get(CONF_REG0):
|
||||
value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE])
|
||||
cg.add_define("USE_NRF52_REG0_VOUT", value)
|
||||
if reg0_config[CONF_UICR_ERASE]:
|
||||
cg.add_define("USE_NRF52_UICR_ERASE")
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
|
||||
async def _dfu_to_code(dfu_config):
|
||||
|
||||
121
esphome/components/nrf52/uicr.cpp
Normal file
121
esphome/components/nrf52/uicr.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_NRF52_REG0_VOUT
|
||||
#include <zephyr/init.h>
|
||||
#include <hal/nrf_power.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
|
||||
extern "C" {
|
||||
void nvmc_config(uint32_t mode);
|
||||
void nvmc_wait();
|
||||
nrfx_err_t nrfx_nvmc_uicr_erase();
|
||||
}
|
||||
|
||||
namespace esphome::nrf52 {
|
||||
|
||||
enum class StatusFlags : uint8_t {
|
||||
OK = 0x00,
|
||||
NEED_RESET = 0x01,
|
||||
NEED_ERASE = 0x02,
|
||||
};
|
||||
|
||||
constexpr StatusFlags &operator|=(StatusFlags &a, StatusFlags b) {
|
||||
a = static_cast<StatusFlags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
||||
return a;
|
||||
}
|
||||
|
||||
constexpr bool operator&(StatusFlags a, StatusFlags b) {
|
||||
return (static_cast<uint8_t>(a) & static_cast<uint8_t>(b)) != 0;
|
||||
}
|
||||
|
||||
static bool regout0_ok() {
|
||||
return (NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) == (USE_NRF52_REG0_VOUT << UICR_REGOUT0_VOUT_Pos);
|
||||
}
|
||||
|
||||
static StatusFlags set_regout0() {
|
||||
/* If the board is powered from USB (high voltage mode),
|
||||
* GPIO output voltage is set to 1.8 volts by default.
|
||||
*/
|
||||
if (!regout0_ok()) {
|
||||
nvmc_config(NVMC_CONFIG_WEN_Wen);
|
||||
NRF_UICR->REGOUT0 =
|
||||
(NRF_UICR->REGOUT0 & ~((uint32_t) UICR_REGOUT0_VOUT_Msk)) | (USE_NRF52_REG0_VOUT << UICR_REGOUT0_VOUT_Pos);
|
||||
nvmc_wait();
|
||||
nvmc_config(NVMC_CONFIG_WEN_Ren);
|
||||
return regout0_ok() ? StatusFlags::NEED_RESET : StatusFlags::NEED_ERASE;
|
||||
}
|
||||
return StatusFlags::OK;
|
||||
}
|
||||
|
||||
#ifndef USE_BOOTLOADER_MCUBOOT
|
||||
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/6a9a6a3e6d0f86918e9286188426a279976645bd/lib/sdk11/components/libraries/bootloader_dfu/dfu_types.h#L61
|
||||
constexpr uint32_t BOOTLOADER_REGION_START = 0x000F4000;
|
||||
constexpr uint32_t BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS = 0x000FE000;
|
||||
|
||||
static bool bootloader_ok() {
|
||||
return NRF_UICR->NRFFW[0] == BOOTLOADER_REGION_START && NRF_UICR->NRFFW[1] == BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS;
|
||||
}
|
||||
|
||||
static StatusFlags fix_bootloader() {
|
||||
if (!bootloader_ok()) {
|
||||
nvmc_config(NVMC_CONFIG_WEN_Wen);
|
||||
NRF_UICR->NRFFW[0] = BOOTLOADER_REGION_START;
|
||||
NRF_UICR->NRFFW[1] = BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS;
|
||||
nvmc_wait();
|
||||
nvmc_config(NVMC_CONFIG_WEN_Ren);
|
||||
return bootloader_ok() ? StatusFlags::NEED_RESET : StatusFlags::NEED_ERASE;
|
||||
}
|
||||
return StatusFlags::OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0]
|
||||
|
||||
static StatusFlags set_uicr() {
|
||||
StatusFlags status = StatusFlags::OK;
|
||||
#ifndef USE_BOOTLOADER_MCUBOOT
|
||||
if (BOOTLOADER_VERSION_REGISTER <= 0x902) {
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("cannot control regout0 for %#x\n", BOOTLOADER_VERSION_REGISTER);
|
||||
#endif
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
status |= set_regout0();
|
||||
}
|
||||
#ifndef USE_BOOTLOADER_MCUBOOT
|
||||
status |= fix_bootloader();
|
||||
#endif
|
||||
return status;
|
||||
}
|
||||
|
||||
static int board_esphome_init() {
|
||||
StatusFlags status = set_uicr();
|
||||
|
||||
#ifdef USE_NRF52_UICR_ERASE
|
||||
if (status & StatusFlags::NEED_ERASE) {
|
||||
nrfx_err_t ret = nrfx_nvmc_uicr_erase();
|
||||
if (ret != NRFX_SUCCESS) {
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("nrfx_nvmc_uicr_erase failed %d\n", ret);
|
||||
#endif
|
||||
} else {
|
||||
status |= set_uicr();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (status & StatusFlags::NEED_RESET) {
|
||||
/* a reset is required for changes to take effect */
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace esphome::nrf52
|
||||
|
||||
static int board_esphome_init() { return esphome::nrf52::board_esphome_init(); }
|
||||
|
||||
SYS_INIT(board_esphome_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
||||
|
||||
#endif
|
||||
@@ -23,7 +23,7 @@ void PCF85063Component::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float PCF85063Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -23,7 +23,7 @@ void PCF8563Component::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float PCF8563Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -35,6 +35,9 @@ DOMAIN = "psram"
|
||||
|
||||
DEPENDENCIES = [PLATFORM_ESP32]
|
||||
|
||||
# PSRAM availability tracking for cross-component coordination
|
||||
KEY_PSRAM_GUARANTEED = "psram_guaranteed"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
psram_ns = cg.esphome_ns.namespace(DOMAIN)
|
||||
@@ -71,6 +74,23 @@ def supported() -> bool:
|
||||
return variant in SPIRAM_MODES
|
||||
|
||||
|
||||
def is_guaranteed() -> bool:
|
||||
"""Check if PSRAM is guaranteed to be available.
|
||||
|
||||
Returns True when PSRAM is configured with both 'disabled: false' and
|
||||
'ignore_not_found: false', meaning the device will fail to boot if PSRAM
|
||||
is not found. This ensures safe use of high buffer configurations that
|
||||
depend on PSRAM.
|
||||
|
||||
This function should be called during code generation (to_code phase) by
|
||||
components that need to know PSRAM availability for configuration decisions.
|
||||
|
||||
Returns:
|
||||
bool: True if PSRAM is guaranteed, False otherwise
|
||||
"""
|
||||
return CORE.data.get(KEY_PSRAM_GUARANTEED, False)
|
||||
|
||||
|
||||
def validate_psram_mode(config):
|
||||
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
|
||||
if config[CONF_SPEED] == "120MHZ":
|
||||
@@ -131,7 +151,22 @@ def get_config_schema(config):
|
||||
|
||||
CONFIG_SCHEMA = get_config_schema
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_psram_mode
|
||||
|
||||
def _store_psram_guaranteed(config):
|
||||
"""Store PSRAM guaranteed status in CORE.data for other components.
|
||||
|
||||
PSRAM is "guaranteed" when it will fail if not found, ensuring safe use
|
||||
of high buffer configurations in network/wifi components.
|
||||
|
||||
Called during final validation to ensure the flag is available
|
||||
before any to_code() functions run.
|
||||
"""
|
||||
psram_guaranteed = not config[CONF_DISABLED] and not config[CONF_IGNORE_NOT_FOUND]
|
||||
CORE.data[KEY_PSRAM_GUARANTEED] = psram_guaranteed
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(validate_psram_mode, _store_psram_guaranteed)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -62,6 +62,7 @@ void RX8130Component::update() { this->read_time(); }
|
||||
void RX8130Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "RX8130:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
void RX8130Component::read_time() {
|
||||
|
||||
@@ -61,6 +61,7 @@ void SNTPComponent::dump_config() {
|
||||
for (auto &server : this->servers_) {
|
||||
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server);
|
||||
}
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
void SNTPComponent::update() {
|
||||
#if !defined(USE_ESP32)
|
||||
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
|
||||
from esphome import automation, external_files
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio, esp32, media_player, psram, speaker
|
||||
from esphome.components import audio, esp32, media_player, network, psram, speaker
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUFFER_SIZE,
|
||||
@@ -32,6 +32,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
AUTO_LOAD = ["audio"]
|
||||
DEPENDENCIES = ["network"]
|
||||
|
||||
CODEOWNERS = ["@kahrendt", "@synesthesiam"]
|
||||
DOMAIN = "media_player"
|
||||
@@ -280,6 +281,18 @@ PIPELINE_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _request_high_performance_networking(config):
|
||||
"""Request high performance networking for streaming media.
|
||||
|
||||
Speaker media player streams audio data, so it always benefits from
|
||||
optimized WiFi and lwip settings regardless of codec support.
|
||||
Called during config validation to ensure flags are set before to_code().
|
||||
"""
|
||||
network.require_high_performance_networking()
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
media_player.media_player_schema(SpeakerMediaPlayer).extend(
|
||||
{
|
||||
@@ -304,6 +317,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.only_with_esp_idf,
|
||||
_validate_repeated_speaker,
|
||||
_request_high_performance_networking,
|
||||
)
|
||||
|
||||
|
||||
@@ -321,28 +335,10 @@ FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config):
|
||||
if CORE.data[DOMAIN][config[CONF_ID].id][CONF_CODEC_SUPPORT_ENABLED]:
|
||||
# Compile all supported audio codecs and optimize the wifi settings
|
||||
|
||||
# Compile all supported audio codecs
|
||||
cg.add_define("USE_AUDIO_FLAC_SUPPORT", True)
|
||||
cg.add_define("USE_AUDIO_MP3_SUPPORT", True)
|
||||
|
||||
# Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 64)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM", 64)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 32)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32)
|
||||
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 65534)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64)
|
||||
|
||||
# Allocate wifi buffers in PSRAM
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True)
|
||||
|
||||
var = await media_player.new_media_player(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ namespace time {
|
||||
static const char *const TAG = "time";
|
||||
|
||||
RealTimeClock::RealTimeClock() = default;
|
||||
|
||||
void RealTimeClock::dump_config() {
|
||||
#ifdef USE_TIME_TIMEZONE
|
||||
ESP_LOGCONFIG(TAG, "Timezone: '%s'", this->timezone_.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
||||
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
|
||||
// Update UTC epoch time.
|
||||
|
||||
@@ -52,6 +52,8 @@ class RealTimeClock : public PollingComponent {
|
||||
this->time_sync_callback_.add(std::move(callback));
|
||||
};
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
/// Report a unix epoch as current time.
|
||||
void synchronize_epoch_(uint32_t epoch);
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import logging
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_USE_PSRAM
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
||||
from esphome.components.network import ip_address_literal
|
||||
from esphome.components.network import (
|
||||
has_high_performance_networking,
|
||||
ip_address_literal,
|
||||
)
|
||||
from esphome.components.psram import is_guaranteed as psram_is_guaranteed
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.config_validation import only_with_esp_idf
|
||||
@@ -42,6 +48,7 @@ from esphome.const import (
|
||||
CONF_TTLS_PHASE_2,
|
||||
CONF_USE_ADDRESS,
|
||||
CONF_USERNAME,
|
||||
Platform,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, HexInt, coroutine_with_priority
|
||||
@@ -49,10 +56,15 @@ import esphome.final_validate as fv
|
||||
|
||||
from . import wpa2_eap
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
AUTO_LOAD = ["network"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2, const.VARIANT_ESP32P4]
|
||||
CONF_SAVE = "save"
|
||||
CONF_MIN_AUTH_MODE = "min_auth_mode"
|
||||
|
||||
# Maximum number of WiFi networks that can be configured
|
||||
# Limited to 127 because selected_sta_index_ is int8_t in C++
|
||||
@@ -70,6 +82,14 @@ WIFI_POWER_SAVE_MODES = {
|
||||
"LIGHT": WiFiPowerSaveMode.WIFI_POWER_SAVE_LIGHT,
|
||||
"HIGH": WiFiPowerSaveMode.WIFI_POWER_SAVE_HIGH,
|
||||
}
|
||||
|
||||
WifiMinAuthMode = wifi_ns.enum("WifiMinAuthMode")
|
||||
WIFI_MIN_AUTH_MODES = {
|
||||
"WPA": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA,
|
||||
"WPA2": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA2,
|
||||
"WPA3": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA3,
|
||||
}
|
||||
VALIDATE_WIFI_MIN_AUTH_MODE = cv.enum(WIFI_MIN_AUTH_MODES, upper=True)
|
||||
WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition)
|
||||
WiFiEnabledCondition = wifi_ns.class_("WiFiEnabledCondition", Condition)
|
||||
WiFiEnableAction = wifi_ns.class_("WiFiEnableAction", automation.Action)
|
||||
@@ -174,7 +194,7 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend(
|
||||
{
|
||||
cv.Optional(CONF_BSSID): cv.mac_address,
|
||||
cv.Optional(CONF_HIDDEN): cv.boolean,
|
||||
cv.Optional(CONF_PRIORITY, default=0.0): cv.float_,
|
||||
cv.Optional(CONF_PRIORITY, default=0): cv.int_range(min=-128, max=127),
|
||||
cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA,
|
||||
}
|
||||
)
|
||||
@@ -187,6 +207,27 @@ def validate_variant(_):
|
||||
raise cv.Invalid(f"WiFi requires component esp32_hosted on {variant}")
|
||||
|
||||
|
||||
def _apply_min_auth_mode_default(config):
|
||||
"""Apply platform-specific default for min_auth_mode and warn ESP8266 users."""
|
||||
# Only apply defaults for platforms that support min_auth_mode
|
||||
if CONF_MIN_AUTH_MODE not in config and (CORE.is_esp8266 or CORE.is_esp32):
|
||||
if CORE.is_esp8266:
|
||||
_LOGGER.warning(
|
||||
"The minimum WiFi authentication mode (wifi -> min_auth_mode) is not set. "
|
||||
"This controls the weakest encryption your device will accept when connecting to WiFi. "
|
||||
"Currently defaults to WPA (less secure), but will change to WPA2 (more secure) in 2026.6.0. "
|
||||
"WPA uses TKIP encryption which has known security vulnerabilities and should be avoided. "
|
||||
"WPA2 uses AES encryption which is significantly more secure. "
|
||||
"To silence this warning, explicitly set min_auth_mode under 'wifi:'. "
|
||||
"If your router supports WPA2 or WPA3, set 'min_auth_mode: WPA2'. "
|
||||
"If your router only supports WPA, set 'min_auth_mode: WPA'."
|
||||
)
|
||||
config[CONF_MIN_AUTH_MODE] = VALIDATE_WIFI_MIN_AUTH_MODE("WPA")
|
||||
elif CORE.is_esp32:
|
||||
config[CONF_MIN_AUTH_MODE] = VALIDATE_WIFI_MIN_AUTH_MODE("WPA2")
|
||||
return config
|
||||
|
||||
|
||||
def final_validate(config):
|
||||
has_sta = bool(config.get(CONF_NETWORKS, True))
|
||||
has_ap = CONF_AP in config
|
||||
@@ -287,6 +328,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
): cv.enum(WIFI_POWER_SAVE_MODES, upper=True),
|
||||
cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional(CONF_MIN_AUTH_MODE): cv.All(
|
||||
VALIDATE_WIFI_MIN_AUTH_MODE,
|
||||
cv.only_on([Platform.ESP32, Platform.ESP8266]),
|
||||
),
|
||||
cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All(
|
||||
cv.decibel, cv.float_range(min=8.5, max=20.5)
|
||||
),
|
||||
@@ -311,6 +356,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
),
|
||||
_apply_min_auth_mode_default,
|
||||
_validate,
|
||||
)
|
||||
|
||||
@@ -385,6 +431,8 @@ async def to_code(config):
|
||||
|
||||
# Track if any network uses Enterprise authentication
|
||||
has_eap = False
|
||||
# Track if any network uses manual IP
|
||||
has_manual_ip = False
|
||||
|
||||
# Initialize FixedVector with the count of networks
|
||||
networks = config.get(CONF_NETWORKS, [])
|
||||
@@ -398,11 +446,15 @@ async def to_code(config):
|
||||
for network in networks:
|
||||
if CONF_EAP in network:
|
||||
has_eap = True
|
||||
if network.get(CONF_MANUAL_IP) or config.get(CONF_MANUAL_IP):
|
||||
has_manual_ip = True
|
||||
cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network)
|
||||
|
||||
if CONF_AP in config:
|
||||
conf = config[CONF_AP]
|
||||
ip_config = conf.get(CONF_MANUAL_IP)
|
||||
if ip_config:
|
||||
has_manual_ip = True
|
||||
cg.with_local_variable(
|
||||
conf[CONF_ID],
|
||||
WiFiAP(),
|
||||
@@ -418,8 +470,14 @@ async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", has_eap)
|
||||
|
||||
# Only define USE_WIFI_MANUAL_IP if any AP uses manual IP
|
||||
if has_manual_ip:
|
||||
cg.add_define("USE_WIFI_MANUAL_IP")
|
||||
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE]))
|
||||
if CONF_MIN_AUTH_MODE in config:
|
||||
cg.add(var.set_min_auth_mode(config[CONF_MIN_AUTH_MODE]))
|
||||
if config[CONF_FAST_CONNECT]:
|
||||
cg.add_define("USE_WIFI_FAST_CONNECT")
|
||||
cg.add(var.set_passive_scan(config[CONF_PASSIVE_SCAN]))
|
||||
@@ -444,6 +502,56 @@ async def to_code(config):
|
||||
|
||||
if config.get(CONF_USE_PSRAM):
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True)
|
||||
|
||||
# Apply high performance WiFi settings if high performance networking is enabled
|
||||
if CORE.is_esp32 and CORE.using_esp_idf and has_high_performance_networking():
|
||||
# Check if PSRAM is guaranteed (set by psram component during final validation)
|
||||
psram_guaranteed = psram_is_guaranteed()
|
||||
|
||||
# Always allocate WiFi buffers in PSRAM if available
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True)
|
||||
|
||||
if psram_guaranteed:
|
||||
_LOGGER.info(
|
||||
"Applying high-performance WiFi settings (PSRAM guaranteed): 512 RX buffers, 32 TX buffers"
|
||||
)
|
||||
# PSRAM is guaranteed - use aggressive settings
|
||||
# Higher maximum values are allowed because CONFIG_LWIP_WND_SCALE is set to true in networking component
|
||||
# Based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702
|
||||
|
||||
# Large dynamic RX buffers (requires PSRAM)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 512)
|
||||
|
||||
# Static TX buffers for better performance
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_TX_BUFFER", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BUFFER_TYPE", 0)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM", 32)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM", 8)
|
||||
|
||||
# AMPDU settings optimized for PSRAM
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 16)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Applying optimized WiFi settings: 64 RX buffers, 64 TX buffers"
|
||||
)
|
||||
# PSRAM not guaranteed - use more conservative, but still optimized settings
|
||||
# Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32
|
||||
|
||||
# Standard buffer counts
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 64)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM", 64)
|
||||
|
||||
# Standard AMPDU settings
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 32)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32)
|
||||
|
||||
cg.add_define("USE_WIFI")
|
||||
|
||||
# must register before OTA safe mode check
|
||||
|
||||
@@ -197,6 +197,10 @@ static constexpr uint8_t WIFI_RETRY_COUNT_PER_SSID = 1;
|
||||
// Rationale: Fast connect prioritizes speed - try each AP once to find a working one quickly
|
||||
static constexpr uint8_t WIFI_RETRY_COUNT_PER_AP = 1;
|
||||
|
||||
/// Cooldown duration in milliseconds after adapter restart or repeated failures
|
||||
/// Allows WiFi hardware to stabilize before next connection attempt
|
||||
static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 1000;
|
||||
|
||||
static constexpr uint8_t get_max_retries_for_phase(WiFiRetryPhase phase) {
|
||||
switch (phase) {
|
||||
case WiFiRetryPhase::INITIAL_CONNECT:
|
||||
@@ -253,17 +257,19 @@ bool WiFiComponent::ssid_was_seen_in_scan_(const std::string &ssid) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index, bool include_explicit_hidden) {
|
||||
int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index) {
|
||||
// Find next SSID that wasn't in scan results (might be hidden)
|
||||
bool include_explicit_hidden = !this->went_through_explicit_hidden_phase_();
|
||||
// Start searching from start_index + 1
|
||||
for (size_t i = start_index + 1; i < this->sta_.size(); i++) {
|
||||
const auto &sta = this->sta_[i];
|
||||
|
||||
// Skip networks that were already tried in EXPLICIT_HIDDEN phase
|
||||
// Those are: networks marked hidden:true that appear before the first non-hidden network
|
||||
// If all networks are hidden (first_non_hidden_idx == -1), skip all of them
|
||||
if (!include_explicit_hidden && sta.get_hidden()) {
|
||||
int8_t first_non_hidden_idx = this->find_first_non_hidden_index_();
|
||||
if (first_non_hidden_idx >= 0 && static_cast<int8_t>(i) < first_non_hidden_idx) {
|
||||
if (first_non_hidden_idx < 0 || static_cast<int8_t>(i) < first_non_hidden_idx) {
|
||||
ESP_LOGD(TAG, "Skipping " LOG_SECRET("'%s'") " (explicit hidden, already tried)", sta.get_ssid().c_str());
|
||||
continue;
|
||||
}
|
||||
@@ -273,7 +279,7 @@ int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index, bool include_exp
|
||||
ESP_LOGD(TAG, "Hidden candidate " LOG_SECRET("'%s'") " at index %d", sta.get_ssid().c_str(), static_cast<int>(i));
|
||||
return static_cast<int8_t>(i);
|
||||
}
|
||||
ESP_LOGD(TAG, "Skipping " LOG_SECRET("'%s'") " (visible in scan)", sta.get_ssid().c_str());
|
||||
ESP_LOGD(TAG, "Skipping hidden retry for visible network " LOG_SECRET("'%s'"), sta.get_ssid().c_str());
|
||||
}
|
||||
// No hidden SSIDs found
|
||||
return -1;
|
||||
@@ -287,7 +293,7 @@ void WiFiComponent::start_initial_connection_() {
|
||||
this->selected_sta_index_ = 0;
|
||||
this->retry_phase_ = WiFiRetryPhase::EXPLICIT_HIDDEN;
|
||||
WiFiAP params = this->build_params_for_current_phase_();
|
||||
this->start_connecting(params, false);
|
||||
this->start_connecting(params);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Starting scan");
|
||||
this->start_scanning();
|
||||
@@ -369,13 +375,13 @@ void WiFiComponent::start() {
|
||||
// Without saved data, try first configured network or use normal flow
|
||||
if (loaded_fast_connect) {
|
||||
ESP_LOGI(TAG, "Starting fast_connect (saved) " LOG_SECRET("'%s'"), params.get_ssid().c_str());
|
||||
this->start_connecting(params, false);
|
||||
this->start_connecting(params);
|
||||
} else if (!this->sta_.empty() && !this->sta_[0].get_hidden()) {
|
||||
// No saved data, but have configured networks - try first non-hidden network
|
||||
ESP_LOGI(TAG, "Starting fast_connect (config) " LOG_SECRET("'%s'"), this->sta_[0].get_ssid().c_str());
|
||||
this->selected_sta_index_ = 0;
|
||||
params = this->build_params_for_current_phase_();
|
||||
this->start_connecting(params, false);
|
||||
this->start_connecting(params);
|
||||
} else {
|
||||
// No saved data and (no networks OR first is hidden) - use normal flow
|
||||
this->start_initial_connection_();
|
||||
@@ -411,8 +417,11 @@ void WiFiComponent::start() {
|
||||
void WiFiComponent::restart_adapter() {
|
||||
ESP_LOGW(TAG, "Restarting adapter");
|
||||
this->wifi_mode_(false, {});
|
||||
delay(100); // NOLINT
|
||||
// Enter cooldown state to allow WiFi hardware to stabilize after restart
|
||||
// Don't set retry_phase_ or num_retried_ here - state machine handles transitions
|
||||
this->state_ = WIFI_COMPONENT_STATE_COOLDOWN;
|
||||
this->action_started_ = millis();
|
||||
this->error_from_callback_ = false;
|
||||
}
|
||||
|
||||
void WiFiComponent::loop() {
|
||||
@@ -432,20 +441,12 @@ void WiFiComponent::loop() {
|
||||
switch (this->state_) {
|
||||
case WIFI_COMPONENT_STATE_COOLDOWN: {
|
||||
this->status_set_warning(LOG_STR("waiting to reconnect"));
|
||||
if (millis() - this->action_started_ > 5000) {
|
||||
// After cooldown, connect based on current retry phase
|
||||
this->reset_selected_ap_to_first_if_invalid_();
|
||||
|
||||
// Check if we need to trigger a scan first
|
||||
if (this->needs_scan_results_() && !this->all_networks_hidden_()) {
|
||||
// Need scan results or no matching networks found - scan/rescan
|
||||
ESP_LOGD(TAG, "Scanning required for phase %s", LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_)));
|
||||
this->start_scanning();
|
||||
} else {
|
||||
// Have everything we need to connect (or all networks are hidden, skip scanning)
|
||||
WiFiAP params = this->build_params_for_current_phase_();
|
||||
this->start_connecting(params, false);
|
||||
}
|
||||
if (now - this->action_started_ > WIFI_COOLDOWN_DURATION_MS) {
|
||||
// After cooldown we either restarted the adapter because of
|
||||
// a failure, or something tried to connect over and over
|
||||
// so we entered cooldown. In both cases we call
|
||||
// check_connecting_finished to continue the state machine.
|
||||
this->check_connecting_finished();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -454,8 +455,7 @@ void WiFiComponent::loop() {
|
||||
this->check_scanning_finished();
|
||||
break;
|
||||
}
|
||||
case WIFI_COMPONENT_STATE_STA_CONNECTING:
|
||||
case WIFI_COMPONENT_STATE_STA_CONNECTING_2: {
|
||||
case WIFI_COMPONENT_STATE_STA_CONNECTING: {
|
||||
this->status_set_warning(LOG_STR("associating to network"));
|
||||
this->check_connecting_finished();
|
||||
break;
|
||||
@@ -569,6 +569,7 @@ void WiFiComponent::setup_ap_config_() {
|
||||
" IP Address: %s",
|
||||
this->ap_.get_ssid().c_str(), this->ap_.get_password().c_str(), ip_address.c_str());
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
auto manual_ip = this->ap_.get_manual_ip();
|
||||
if (manual_ip.has_value()) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
@@ -578,6 +579,7 @@ void WiFiComponent::setup_ap_config_() {
|
||||
manual_ip->static_ip.str().c_str(), manual_ip->gateway.str().c_str(),
|
||||
manual_ip->subnet.str().c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!this->has_sta()) {
|
||||
this->state_ = WIFI_COMPONENT_STATE_AP;
|
||||
@@ -664,10 +666,10 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa
|
||||
this->set_sta(sta);
|
||||
}
|
||||
|
||||
void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
|
||||
void WiFiComponent::start_connecting(const WiFiAP &ap) {
|
||||
// Log connection attempt at INFO level with priority
|
||||
std::string bssid_formatted;
|
||||
float priority = 0.0f;
|
||||
int8_t priority = 0;
|
||||
|
||||
if (ap.get_bssid().has_value()) {
|
||||
bssid_formatted = format_mac_address_pretty(ap.get_bssid().value().data());
|
||||
@@ -675,7 +677,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Connecting to " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") " (priority %.1f, attempt %u/%u in phase %s)...",
|
||||
"Connecting to " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") " (priority %d, attempt %u/%u in phase %s)...",
|
||||
ap.get_ssid().c_str(), ap.get_bssid().has_value() ? bssid_formatted.c_str() : LOG_STR_LITERAL("any"),
|
||||
priority, this->num_retried_ + 1, get_max_retries_for_phase(this->retry_phase_),
|
||||
LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_)));
|
||||
@@ -716,11 +718,14 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
|
||||
} else {
|
||||
ESP_LOGV(TAG, " Channel not set");
|
||||
}
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (ap.get_manual_ip().has_value()) {
|
||||
ManualIP m = *ap.get_manual_ip();
|
||||
ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str().c_str(),
|
||||
m.gateway.str().c_str(), m.subnet.str().c_str(), m.dns1.str().c_str(), m.dns2.str().c_str());
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
ESP_LOGV(TAG, " Using DHCP IP");
|
||||
}
|
||||
ESP_LOGV(TAG, " Hidden: %s", YESNO(ap.get_hidden()));
|
||||
@@ -728,14 +733,11 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
|
||||
|
||||
if (!this->wifi_sta_connect_(ap)) {
|
||||
ESP_LOGE(TAG, "wifi_sta_connect_ failed");
|
||||
this->retry_connect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!two) {
|
||||
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING;
|
||||
// Enter cooldown to allow WiFi hardware to stabilize
|
||||
// (immediate failure suggests hardware not ready, different from connection timeout)
|
||||
this->state_ = WIFI_COMPONENT_STATE_COOLDOWN;
|
||||
} else {
|
||||
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING_2;
|
||||
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING;
|
||||
}
|
||||
this->action_started_ = millis();
|
||||
}
|
||||
@@ -812,7 +814,7 @@ void WiFiComponent::print_connect_params_() {
|
||||
wifi_gateway_ip_().str().c_str(), wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str());
|
||||
#ifdef ESPHOME_LOG_HAS_VERBOSE
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid().has_value()) {
|
||||
ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*config->get_bssid()));
|
||||
ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(*config->get_bssid()));
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WIFI_11KV_SUPPORT
|
||||
@@ -933,8 +935,7 @@ __attribute__((noinline)) static void log_scan_result(const WiFiScanResult &res)
|
||||
ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(),
|
||||
res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s,
|
||||
LOG_STR_ARG(get_signal_bars(res.get_rssi())));
|
||||
ESP_LOGD(TAG, " Channel: %2u, RSSI: %3d dB, Priority: %4.1f", res.get_channel(), res.get_rssi(),
|
||||
res.get_priority());
|
||||
ESP_LOGD(TAG, " Channel: %2u, RSSI: %3d dB, Priority: %4d", res.get_channel(), res.get_rssi(), res.get_priority());
|
||||
} else {
|
||||
ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s,
|
||||
LOG_STR_ARG(get_signal_bars(res.get_rssi())));
|
||||
@@ -1003,6 +1004,10 @@ void WiFiComponent::check_scanning_finished() {
|
||||
// No scan results matched our configured networks - transition directly to hidden mode
|
||||
// Don't call retry_connect() since we never attempted a connection (no BSSID to penalize)
|
||||
this->transition_to_phase_(WiFiRetryPhase::RETRY_HIDDEN);
|
||||
// If no hidden networks to try, skip connection attempt (will be handled on next loop)
|
||||
if (this->selected_sta_index_ == -1) {
|
||||
return;
|
||||
}
|
||||
// Now start connection attempt in hidden mode
|
||||
} else if (this->transition_to_phase_(WiFiRetryPhase::SCAN_CONNECTING)) {
|
||||
return; // scan started, wait for next loop iteration
|
||||
@@ -1013,7 +1018,7 @@ void WiFiComponent::check_scanning_finished() {
|
||||
WiFiAP params = this->build_params_for_current_phase_();
|
||||
// Ensure we're in SCAN_CONNECTING phase when connecting with scan results
|
||||
// (needed when scan was started directly without transition_to_phase_, e.g., initial scan)
|
||||
this->start_connecting(params, false);
|
||||
this->start_connecting(params);
|
||||
}
|
||||
|
||||
void WiFiComponent::dump_config() {
|
||||
@@ -1063,6 +1068,9 @@ void WiFiComponent::check_connecting_finished() {
|
||||
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED;
|
||||
this->num_retried_ = 0;
|
||||
|
||||
// Clear priority tracking if all priorities are at minimum
|
||||
this->clear_priorities_if_all_min_();
|
||||
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
this->save_fast_connect_settings_();
|
||||
#endif
|
||||
@@ -1084,7 +1092,7 @@ void WiFiComponent::check_connecting_finished() {
|
||||
}
|
||||
|
||||
if (this->error_from_callback_) {
|
||||
ESP_LOGW(TAG, "Connecting to network failed");
|
||||
ESP_LOGW(TAG, "Connecting to network failed (callback)");
|
||||
this->retry_connect();
|
||||
return;
|
||||
}
|
||||
@@ -1142,7 +1150,12 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() {
|
||||
return WiFiRetryPhase::EXPLICIT_HIDDEN;
|
||||
}
|
||||
|
||||
// No more consecutive explicitly hidden networks - proceed to scanning
|
||||
// No more consecutive explicitly hidden networks
|
||||
// If ALL networks are hidden, skip scanning and go directly to restart
|
||||
if (this->find_first_non_hidden_index_() < 0) {
|
||||
return WiFiRetryPhase::RESTARTING_ADAPTER;
|
||||
}
|
||||
// Otherwise proceed to scanning for non-hidden networks
|
||||
return WiFiRetryPhase::SCAN_CONNECTING;
|
||||
}
|
||||
|
||||
@@ -1160,14 +1173,12 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() {
|
||||
// Its priority has been decreased, so on next scan it will be sorted lower
|
||||
// and we'll try the next best BSSID.
|
||||
// Check if there are any potentially hidden networks to try
|
||||
if (this->find_next_hidden_sta_(-1, !this->went_through_explicit_hidden_phase_()) >= 0) {
|
||||
if (this->find_next_hidden_sta_(-1) >= 0) {
|
||||
return WiFiRetryPhase::RETRY_HIDDEN; // Found hidden networks to try
|
||||
}
|
||||
// No hidden networks - skip directly to restart/rescan
|
||||
if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) {
|
||||
return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN
|
||||
: WiFiRetryPhase::SCAN_CONNECTING;
|
||||
}
|
||||
// No hidden networks - always go through RESTARTING_ADAPTER phase
|
||||
// This ensures num_retried_ gets reset and a fresh scan is triggered
|
||||
// The actual adapter restart will be skipped if captive portal/improv is active
|
||||
return WiFiRetryPhase::RESTARTING_ADAPTER;
|
||||
|
||||
case WiFiRetryPhase::RETRY_HIDDEN:
|
||||
@@ -1179,20 +1190,18 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() {
|
||||
|
||||
// Exhausted retries on current SSID - check if there are more potentially hidden SSIDs to try
|
||||
if (this->selected_sta_index_ < static_cast<int8_t>(this->sta_.size()) - 1) {
|
||||
// More SSIDs available - stay in RETRY_HIDDEN, advance will happen in retry_connect()
|
||||
return WiFiRetryPhase::RETRY_HIDDEN;
|
||||
// Check if find_next_hidden_sta_() would actually find another hidden SSID
|
||||
// as it might have been seen in the scan results and we want to skip those
|
||||
// otherwise we will get stuck in RETRY_HIDDEN phase
|
||||
if (this->find_next_hidden_sta_(this->selected_sta_index_) != -1) {
|
||||
// More hidden SSIDs available - stay in RETRY_HIDDEN, advance will happen in retry_connect()
|
||||
return WiFiRetryPhase::RETRY_HIDDEN;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Exhausted all potentially hidden SSIDs - rescan to try next BSSID
|
||||
// If captive portal/improv is active, skip adapter restart and go back to start
|
||||
// Otherwise restart adapter to clear any stuck state
|
||||
if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) {
|
||||
// Go back to explicit hidden if we went through it initially, otherwise scan
|
||||
return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN
|
||||
: WiFiRetryPhase::SCAN_CONNECTING;
|
||||
}
|
||||
|
||||
// Restart adapter
|
||||
// Exhausted all potentially hidden SSIDs - always go through RESTARTING_ADAPTER
|
||||
// This ensures num_retried_ gets reset and a fresh scan is triggered
|
||||
// The actual adapter restart will be skipped if captive portal/improv is active
|
||||
return WiFiRetryPhase::RESTARTING_ADAPTER;
|
||||
|
||||
case WiFiRetryPhase::RESTARTING_ADAPTER:
|
||||
@@ -1212,8 +1221,8 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() {
|
||||
/// - Performing phase-specific initialization (e.g., advancing AP index, starting scans)
|
||||
///
|
||||
/// @param new_phase The phase we're transitioning TO
|
||||
/// @return true if an async scan was started (caller should wait for completion)
|
||||
/// false if no scan started (caller can proceed with connection attempt)
|
||||
/// @return true if connection attempt should be skipped (scan started or no networks to try)
|
||||
/// false if caller can proceed with connection attempt
|
||||
bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) {
|
||||
WiFiRetryPhase old_phase = this->retry_phase_;
|
||||
|
||||
@@ -1271,7 +1280,7 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) {
|
||||
// If first network is marked hidden, we went through EXPLICIT_HIDDEN phase
|
||||
// In that case, skip networks marked hidden:true (already tried)
|
||||
// Otherwise, include them (they haven't been tried yet)
|
||||
this->selected_sta_index_ = this->find_next_hidden_sta_(-1, !this->went_through_explicit_hidden_phase_());
|
||||
this->selected_sta_index_ = this->find_next_hidden_sta_(-1);
|
||||
|
||||
if (this->selected_sta_index_ == -1) {
|
||||
ESP_LOGD(TAG, "All SSIDs visible or already tried, skipping hidden mode");
|
||||
@@ -1280,7 +1289,12 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) {
|
||||
break;
|
||||
|
||||
case WiFiRetryPhase::RESTARTING_ADAPTER:
|
||||
this->restart_adapter();
|
||||
// Skip actual adapter restart if captive portal/improv is active
|
||||
// This allows state machine to reset num_retried_ and trigger fresh scan
|
||||
// without disrupting the captive portal/improv connection
|
||||
if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_()) {
|
||||
this->restart_adapter();
|
||||
}
|
||||
// Return true to indicate we should wait (go to COOLDOWN) instead of immediately connecting
|
||||
return true;
|
||||
|
||||
@@ -1291,6 +1305,34 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) {
|
||||
return false; // Did not start scan, can proceed with connection
|
||||
}
|
||||
|
||||
/// Clear BSSID priority tracking if all priorities are at minimum (saves memory)
|
||||
/// At minimum priority, all BSSIDs are equally bad, so priority tracking is useless
|
||||
/// Called after successful connection or after failed connection attempts
|
||||
void WiFiComponent::clear_priorities_if_all_min_() {
|
||||
if (this->sta_priorities_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int8_t first_priority = this->sta_priorities_[0].priority;
|
||||
|
||||
// Only clear if all priorities have been decremented to the minimum value
|
||||
// At this point, all BSSIDs have been equally penalized and priority info is useless
|
||||
if (first_priority != std::numeric_limits<int8_t>::min()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &pri : this->sta_priorities_) {
|
||||
if (pri.priority != first_priority) {
|
||||
return; // Not all same, nothing to do
|
||||
}
|
||||
}
|
||||
|
||||
// All priorities are at minimum - clear the vector to save memory and reset
|
||||
ESP_LOGD(TAG, "Clearing BSSID priorities (all at minimum)");
|
||||
this->sta_priorities_.clear();
|
||||
this->sta_priorities_.shrink_to_fit();
|
||||
}
|
||||
|
||||
/// Log failed connection attempt and decrease BSSID priority to avoid repeated failures
|
||||
/// This function identifies which BSSID was attempted (from scan results or config),
|
||||
/// decreases its priority by 1.0 to discourage future attempts, and logs the change.
|
||||
@@ -1304,6 +1346,11 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) {
|
||||
/// - Other phases: Uses BSSID from config if explicitly specified by user or fast_connect
|
||||
///
|
||||
/// If no BSSID is available (SSID-only connection), priority adjustment is skipped.
|
||||
///
|
||||
/// IMPORTANT: Priority is only decreased on the LAST attempt for a BSSID in SCAN_CONNECTING phase.
|
||||
/// This prevents false positives from transient WiFi stack state issues after scanning.
|
||||
/// Single failures don't necessarily mean the AP is bad - two genuine failures provide
|
||||
/// higher confidence before degrading priority and skipping the BSSID in future scans.
|
||||
void WiFiComponent::log_and_adjust_priority_for_failed_connect_() {
|
||||
// Determine which BSSID we tried to connect to
|
||||
optional<bssid_t> failed_bssid;
|
||||
@@ -1320,11 +1367,6 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() {
|
||||
return; // No BSSID to penalize
|
||||
}
|
||||
|
||||
// Decrease priority to avoid repeatedly trying the same failed BSSID
|
||||
float old_priority = this->get_sta_priority(failed_bssid.value());
|
||||
float new_priority = old_priority - 1.0f;
|
||||
this->set_sta_priority(failed_bssid.value(), new_priority);
|
||||
|
||||
// Get SSID for logging
|
||||
std::string ssid;
|
||||
if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) {
|
||||
@@ -1333,8 +1375,27 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() {
|
||||
ssid = config->get_ssid();
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %.1f → %.1f", ssid.c_str(),
|
||||
// Only decrease priority on the last attempt for this phase
|
||||
// This prevents false positives from transient WiFi stack issues
|
||||
uint8_t max_retries = get_max_retries_for_phase(this->retry_phase_);
|
||||
bool is_last_attempt = (this->num_retried_ + 1 >= max_retries);
|
||||
|
||||
// Decrease priority only on last attempt to avoid false positives from transient failures
|
||||
int8_t old_priority = this->get_sta_priority(failed_bssid.value());
|
||||
int8_t new_priority = old_priority;
|
||||
|
||||
if (is_last_attempt) {
|
||||
// Decrease priority, but clamp to int8_t::min to prevent overflow
|
||||
new_priority =
|
||||
(old_priority > std::numeric_limits<int8_t>::min()) ? (old_priority - 1) : std::numeric_limits<int8_t>::min();
|
||||
this->set_sta_priority(failed_bssid.value(), new_priority);
|
||||
}
|
||||
ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid.c_str(),
|
||||
format_mac_address_pretty(failed_bssid.value().data()).c_str(), old_priority, new_priority);
|
||||
|
||||
// After adjusting priority, check if all priorities are now at minimum
|
||||
// If so, clear the vector to save memory and reset for fresh start
|
||||
this->clear_priorities_if_all_min_();
|
||||
}
|
||||
|
||||
/// Handle target advancement or retry counter increment when staying in the same phase
|
||||
@@ -1379,8 +1440,7 @@ void WiFiComponent::advance_to_next_target_or_increment_retry_() {
|
||||
// If first network is marked hidden, we went through EXPLICIT_HIDDEN phase
|
||||
// In that case, skip networks marked hidden:true (already tried)
|
||||
// Otherwise, include them (they haven't been tried yet)
|
||||
int8_t next_index =
|
||||
this->find_next_hidden_sta_(this->selected_sta_index_, !this->went_through_explicit_hidden_phase_());
|
||||
int8_t next_index = this->find_next_hidden_sta_(this->selected_sta_index_);
|
||||
if (next_index != -1) {
|
||||
// Found another potentially hidden SSID
|
||||
this->selected_sta_index_ = next_index;
|
||||
@@ -1408,15 +1468,13 @@ void WiFiComponent::advance_to_next_target_or_increment_retry_() {
|
||||
void WiFiComponent::retry_connect() {
|
||||
this->log_and_adjust_priority_for_failed_connect_();
|
||||
|
||||
delay(10);
|
||||
|
||||
// Determine next retry phase based on current state
|
||||
WiFiRetryPhase current_phase = this->retry_phase_;
|
||||
WiFiRetryPhase next_phase = this->determine_next_phase_();
|
||||
|
||||
// Handle phase transitions (transition_to_phase_ handles same-phase no-op internally)
|
||||
if (this->transition_to_phase_(next_phase)) {
|
||||
return; // Wait for scan to complete
|
||||
return; // Scan started or adapter restarted (which sets its own state)
|
||||
}
|
||||
|
||||
if (next_phase == current_phase) {
|
||||
@@ -1425,22 +1483,14 @@ void WiFiComponent::retry_connect() {
|
||||
|
||||
this->error_from_callback_ = false;
|
||||
|
||||
if (this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTING) {
|
||||
yield();
|
||||
// Check if we have a valid target before building params
|
||||
// After exhausting all networks in a phase, selected_sta_index_ may be -1
|
||||
// In that case, skip connection and let next wifi_loop() handle phase transition
|
||||
if (this->selected_sta_index_ >= 0) {
|
||||
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING_2;
|
||||
WiFiAP params = this->build_params_for_current_phase_();
|
||||
this->start_connecting(params, true);
|
||||
return;
|
||||
}
|
||||
// No valid target - fall through to set state to allow phase transition
|
||||
yield();
|
||||
// Check if we have a valid target before building params
|
||||
// After exhausting all networks in a phase, selected_sta_index_ may be -1
|
||||
// In that case, skip connection and let next wifi_loop() handle phase transition
|
||||
if (this->selected_sta_index_ >= 0) {
|
||||
WiFiAP params = this->build_params_for_current_phase_();
|
||||
this->start_connecting(params);
|
||||
}
|
||||
|
||||
this->state_ = WIFI_COMPONENT_STATE_COOLDOWN;
|
||||
this->action_started_ = millis();
|
||||
}
|
||||
|
||||
void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||
@@ -1532,7 +1582,9 @@ void WiFiAP::set_password(const std::string &password) { this->password_ = passw
|
||||
void WiFiAP::set_eap(optional<EAPAuth> eap_auth) { this->eap_ = std::move(eap_auth); }
|
||||
#endif
|
||||
void WiFiAP::set_channel(optional<uint8_t> channel) { this->channel_ = channel; }
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
void WiFiAP::set_manual_ip(optional<ManualIP> manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
#endif
|
||||
void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; }
|
||||
const std::string &WiFiAP::get_ssid() const { return this->ssid_; }
|
||||
const optional<bssid_t> &WiFiAP::get_bssid() const { return this->bssid_; }
|
||||
@@ -1541,15 +1593,17 @@ const std::string &WiFiAP::get_password() const { return this->password_; }
|
||||
const optional<EAPAuth> &WiFiAP::get_eap() const { return this->eap_; }
|
||||
#endif
|
||||
const optional<uint8_t> &WiFiAP::get_channel() const { return this->channel_; }
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
const optional<ManualIP> &WiFiAP::get_manual_ip() const { return this->manual_ip_; }
|
||||
#endif
|
||||
bool WiFiAP::get_hidden() const { return this->hidden_; }
|
||||
|
||||
WiFiScanResult::WiFiScanResult(const bssid_t &bssid, std::string ssid, uint8_t channel, int8_t rssi, bool with_auth,
|
||||
bool is_hidden)
|
||||
: bssid_(bssid),
|
||||
ssid_(std::move(ssid)),
|
||||
channel_(channel),
|
||||
rssi_(rssi),
|
||||
ssid_(std::move(ssid)),
|
||||
with_auth_(with_auth),
|
||||
is_hidden_(is_hidden) {}
|
||||
bool WiFiScanResult::matches(const WiFiAP &config) const {
|
||||
|
||||
@@ -74,12 +74,6 @@ enum WiFiComponentState : uint8_t {
|
||||
WIFI_COMPONENT_STATE_STA_SCANNING,
|
||||
/** WiFi is in STA(+AP) mode and currently connecting to an AP. */
|
||||
WIFI_COMPONENT_STATE_STA_CONNECTING,
|
||||
/** WiFi is in STA(+AP) mode and currently connecting to an AP a second time.
|
||||
*
|
||||
* This is required because for some reason ESPs don't like to connect to WiFi APs directly after
|
||||
* a scan.
|
||||
* */
|
||||
WIFI_COMPONENT_STATE_STA_CONNECTING_2,
|
||||
/** WiFi is in STA(+AP) mode and successfully connected. */
|
||||
WIFI_COMPONENT_STATE_STA_CONNECTED,
|
||||
/** WiFi is in AP-only mode and internal AP is already enabled. */
|
||||
@@ -157,8 +151,10 @@ class WiFiAP {
|
||||
void set_eap(optional<EAPAuth> eap_auth);
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
void set_channel(optional<uint8_t> channel);
|
||||
void set_priority(float priority) { priority_ = priority; }
|
||||
void set_priority(int8_t priority) { priority_ = priority; }
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
void set_manual_ip(optional<ManualIP> manual_ip);
|
||||
#endif
|
||||
void set_hidden(bool hidden);
|
||||
const std::string &get_ssid() const;
|
||||
const optional<bssid_t> &get_bssid() const;
|
||||
@@ -167,8 +163,10 @@ class WiFiAP {
|
||||
const optional<EAPAuth> &get_eap() const;
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
const optional<uint8_t> &get_channel() const;
|
||||
float get_priority() const { return priority_; }
|
||||
int8_t get_priority() const { return priority_; }
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
const optional<ManualIP> &get_manual_ip() const;
|
||||
#endif
|
||||
bool get_hidden() const;
|
||||
|
||||
protected:
|
||||
@@ -178,9 +176,11 @@ class WiFiAP {
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
optional<EAPAuth> eap_;
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
optional<ManualIP> manual_ip_;
|
||||
float priority_{0};
|
||||
#endif
|
||||
optional<uint8_t> channel_;
|
||||
int8_t priority_{0};
|
||||
bool hidden_{false};
|
||||
};
|
||||
|
||||
@@ -198,17 +198,17 @@ class WiFiScanResult {
|
||||
int8_t get_rssi() const;
|
||||
bool get_with_auth() const;
|
||||
bool get_is_hidden() const;
|
||||
float get_priority() const { return priority_; }
|
||||
void set_priority(float priority) { priority_ = priority; }
|
||||
int8_t get_priority() const { return priority_; }
|
||||
void set_priority(int8_t priority) { priority_ = priority; }
|
||||
|
||||
bool operator==(const WiFiScanResult &rhs) const;
|
||||
|
||||
protected:
|
||||
bssid_t bssid_;
|
||||
std::string ssid_;
|
||||
float priority_{0.0f};
|
||||
uint8_t channel_;
|
||||
int8_t rssi_;
|
||||
std::string ssid_;
|
||||
int8_t priority_{0};
|
||||
bool matches_{false};
|
||||
bool with_auth_;
|
||||
bool is_hidden_;
|
||||
@@ -216,7 +216,7 @@ class WiFiScanResult {
|
||||
|
||||
struct WiFiSTAPriority {
|
||||
bssid_t bssid;
|
||||
float priority;
|
||||
int8_t priority;
|
||||
};
|
||||
|
||||
enum WiFiPowerSaveMode : uint8_t {
|
||||
@@ -225,6 +225,12 @@ enum WiFiPowerSaveMode : uint8_t {
|
||||
WIFI_POWER_SAVE_HIGH,
|
||||
};
|
||||
|
||||
enum WifiMinAuthMode : uint8_t {
|
||||
WIFI_MIN_AUTH_MODE_WPA = 0,
|
||||
WIFI_MIN_AUTH_MODE_WPA2,
|
||||
WIFI_MIN_AUTH_MODE_WPA3,
|
||||
};
|
||||
|
||||
#ifdef USE_ESP32
|
||||
struct IDFWiFiEvent;
|
||||
#endif
|
||||
@@ -263,7 +269,9 @@ class WiFiComponent : public Component {
|
||||
bool is_disabled();
|
||||
void start_scanning();
|
||||
void check_scanning_finished();
|
||||
void start_connecting(const WiFiAP &ap, bool two);
|
||||
void start_connecting(const WiFiAP &ap);
|
||||
// Backward compatibility overload - ignores 'two' parameter
|
||||
void start_connecting(const WiFiAP &ap, bool /* two */) { this->start_connecting(ap); }
|
||||
|
||||
void check_connecting_finished();
|
||||
|
||||
@@ -274,6 +282,7 @@ class WiFiComponent : public Component {
|
||||
bool is_connected();
|
||||
|
||||
void set_power_save_mode(WiFiPowerSaveMode power_save);
|
||||
void set_min_auth_mode(WifiMinAuthMode min_auth_mode) { min_auth_mode_ = min_auth_mode; }
|
||||
void set_output_power(float output_power) { output_power_ = output_power; }
|
||||
|
||||
void set_passive_scan(bool passive);
|
||||
@@ -317,14 +326,14 @@ class WiFiComponent : public Component {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
float get_sta_priority(const bssid_t bssid) {
|
||||
int8_t get_sta_priority(const bssid_t bssid) {
|
||||
for (auto &it : this->sta_priorities_) {
|
||||
if (it.bssid == bssid)
|
||||
return it.priority;
|
||||
}
|
||||
return 0.0f;
|
||||
return 0;
|
||||
}
|
||||
void set_sta_priority(const bssid_t bssid, float priority) {
|
||||
void set_sta_priority(const bssid_t bssid, int8_t priority) {
|
||||
for (auto &it : this->sta_priorities_) {
|
||||
if (it.bssid == bssid) {
|
||||
it.priority = priority;
|
||||
@@ -379,10 +388,11 @@ class WiFiComponent : public Component {
|
||||
/// Find next SSID that wasn't in scan results (might be hidden)
|
||||
/// Returns index of next potentially hidden SSID, or -1 if none found
|
||||
/// @param start_index Start searching from index after this (-1 to start from beginning)
|
||||
/// @param include_explicit_hidden If true, include SSIDs marked hidden:true. If false, only find truly hidden SSIDs.
|
||||
int8_t find_next_hidden_sta_(int8_t start_index, bool include_explicit_hidden = true);
|
||||
int8_t find_next_hidden_sta_(int8_t start_index);
|
||||
/// Log failed connection and decrease BSSID priority to avoid repeated attempts
|
||||
void log_and_adjust_priority_for_failed_connect_();
|
||||
/// Clear BSSID priority tracking if all priorities are at minimum (saves memory)
|
||||
void clear_priorities_if_all_min_();
|
||||
/// Advance to next target (AP/SSID) within current phase, or increment retry counter
|
||||
/// Called when staying in the same phase after a failed connection attempt
|
||||
void advance_to_next_target_or_increment_retry_();
|
||||
@@ -489,6 +499,7 @@ class WiFiComponent : public Component {
|
||||
// Group all 8-bit values together
|
||||
WiFiComponentState state_{WIFI_COMPONENT_STATE_OFF};
|
||||
WiFiPowerSaveMode power_save_{WIFI_POWER_SAVE_NONE};
|
||||
WifiMinAuthMode min_auth_mode_{WIFI_MIN_AUTH_MODE_WPA2};
|
||||
WiFiRetryPhase retry_phase_{WiFiRetryPhase::INITIAL_CONNECT};
|
||||
uint8_t num_retried_{0};
|
||||
// Index into sta_ array for the currently selected AP configuration (-1 = none selected)
|
||||
|
||||
@@ -258,8 +258,17 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
if (ap.get_password().empty()) {
|
||||
conf.threshold.authmode = AUTH_OPEN;
|
||||
} else {
|
||||
// Only allow auth modes with at least WPA
|
||||
conf.threshold.authmode = AUTH_WPA_PSK;
|
||||
// Set threshold based on configured minimum auth mode
|
||||
// Note: ESP8266 doesn't support WPA3
|
||||
switch (this->min_auth_mode_) {
|
||||
case WIFI_MIN_AUTH_MODE_WPA:
|
||||
conf.threshold.authmode = AUTH_WPA_PSK;
|
||||
break;
|
||||
case WIFI_MIN_AUTH_MODE_WPA2:
|
||||
case WIFI_MIN_AUTH_MODE_WPA3: // Fall back to WPA2 for ESP8266
|
||||
conf.threshold.authmode = AUTH_WPA2_PSK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
conf.threshold.rssi = -127;
|
||||
#endif
|
||||
@@ -273,9 +282,15 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_sta_ip_config_({})) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// setup enterprise authentication if required
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
@@ -823,10 +838,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_ap_ip_config_({})) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -308,7 +308,18 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
if (ap.get_password().empty()) {
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_OPEN;
|
||||
} else {
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK;
|
||||
// Set threshold based on configured minimum auth mode
|
||||
switch (this->min_auth_mode_) {
|
||||
case WIFI_MIN_AUTH_MODE_WPA:
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_WPA_PSK;
|
||||
break;
|
||||
case WIFI_MIN_AUTH_MODE_WPA2:
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||||
break;
|
||||
case WIFI_MIN_AUTH_MODE_WPA3:
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_WPA3_PSK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
@@ -347,8 +358,6 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
// The minimum rssi to accept in the fast scan mode
|
||||
conf.sta.threshold.rssi = -127;
|
||||
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_OPEN;
|
||||
|
||||
wifi_config_t current_conf;
|
||||
esp_err_t err;
|
||||
err = esp_wifi_get_config(WIFI_IF_STA, ¤t_conf);
|
||||
@@ -371,9 +380,15 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_sta_ip_config_({})) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// setup enterprise authentication if required
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
@@ -985,10 +1000,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||
ESP_LOGE(TAG, "wifi_ap_ip_config_ failed:");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_ap_ip_config_({})) {
|
||||
ESP_LOGE(TAG, "wifi_ap_ip_config_ failed:");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -112,9 +112,15 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
WiFi.disconnect();
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_sta_ip_config_({})) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
this->wifi_apply_hostname_();
|
||||
|
||||
@@ -445,10 +451,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
if (!this->wifi_mode_({}, true))
|
||||
return false;
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_ap_ip_config_({})) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
yield();
|
||||
|
||||
|
||||
@@ -55,8 +55,13 @@ bool WiFiComponent::wifi_apply_power_save_() {
|
||||
bool WiFiComponent::wifi_apply_output_power_(float output_power) { return true; }
|
||||
|
||||
bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_sta_ip_config_(ap.get_manual_ip()))
|
||||
return false;
|
||||
#else
|
||||
if (!this->wifi_sta_ip_config_({}))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
auto ret = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().c_str());
|
||||
if (ret != WL_CONNECTED)
|
||||
@@ -161,10 +166,17 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
if (!this->wifi_mode_({}, true))
|
||||
return false;
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_ap_ip_config_({})) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.get_channel().value_or(1));
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.11.0-dev"
|
||||
__version__ = "2025.11.0b1"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
#define USE_TIME_TIMEZONE
|
||||
#define USE_WIFI
|
||||
#define USE_WIFI_AP
|
||||
#define USE_WIFI_MANUAL_IP
|
||||
#define USE_WIREGUARD
|
||||
#endif
|
||||
|
||||
@@ -215,7 +216,6 @@
|
||||
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 2)
|
||||
#define USE_ETHERNET
|
||||
#define USE_ETHERNET_KSZ8081
|
||||
#define USE_ETHERNET_MANUAL_IP
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
@@ -288,6 +288,8 @@
|
||||
|
||||
#ifdef USE_NRF52
|
||||
#define USE_NRF52_DFU
|
||||
#define USE_NRF52_REG0_VOUT 5
|
||||
#define USE_NRF52_UICR_ERASE
|
||||
#define USE_SOFTDEVICE_ID 7
|
||||
#define USE_SOFTDEVICE_VERSION 1
|
||||
#endif
|
||||
|
||||
@@ -6,3 +6,7 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ZEPHYR
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
1
tests/components/debug/test.nrf52-xiao-ble.yaml
Normal file
1
tests/components/debug/test.nrf52-xiao-ble.yaml
Normal file
@@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
||||
4
tests/components/ds1307/test.nrf52-adafruit.yaml
Normal file
4
tests/components/ds1307/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1 +1,4 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
network:
|
||||
enable_high_performance: true
|
||||
|
||||
@@ -15,3 +15,6 @@ nrf52:
|
||||
inverted: true
|
||||
mode:
|
||||
output: true
|
||||
reg0:
|
||||
voltage: 2.1V
|
||||
uicr_erase: true
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
nrf52:
|
||||
reg0:
|
||||
voltage: 3.3V
|
||||
uicr_erase: true
|
||||
|
||||
@@ -5,3 +5,5 @@ nrf52:
|
||||
inverted: true
|
||||
mode:
|
||||
output: true
|
||||
reg0:
|
||||
voltage: 1.8V
|
||||
|
||||
4
tests/components/pcf85063/test.nrf52-adafruit.yaml
Normal file
4
tests/components/pcf85063/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/pcf8563/test.nrf52-adafruit.yaml
Normal file
4
tests/components/pcf8563/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/rx8130/test.nrf52-adafruit.yaml
Normal file
4
tests/components/rx8130/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
7
tests/components/ssd1306_i2c/test.nrf52-xiao-ble.yaml
Normal file
7
tests/components/ssd1306_i2c/test.nrf52-xiao-ble.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
substitutions:
|
||||
reset_pin: P0.10
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -15,5 +15,10 @@ wifi:
|
||||
networks:
|
||||
- ssid: MySSID
|
||||
password: password1
|
||||
priority: 10
|
||||
- ssid: MySSID2
|
||||
password: password2
|
||||
priority: 5
|
||||
- ssid: MySSID3
|
||||
password: password3
|
||||
priority: 0
|
||||
|
||||
@@ -2,6 +2,22 @@ psram:
|
||||
|
||||
wifi:
|
||||
use_psram: true
|
||||
min_auth_mode: WPA
|
||||
manual_ip:
|
||||
static_ip: 192.168.1.100
|
||||
gateway: 192.168.1.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 1.1.1.1
|
||||
dns2: 8.8.8.8
|
||||
ap:
|
||||
ssid: Fallback AP
|
||||
password: fallback_password
|
||||
manual_ip:
|
||||
static_ip: 192.168.4.1
|
||||
gateway: 192.168.4.1
|
||||
subnet: 255.255.255.0
|
||||
|
||||
captive_portal:
|
||||
|
||||
packages:
|
||||
- !include common.yaml
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
<<: !include common.yaml
|
||||
wifi:
|
||||
min_auth_mode: WPA2
|
||||
|
||||
packages:
|
||||
- !include common.yaml
|
||||
|
||||
Reference in New Issue
Block a user