Merge remote-tracking branch 'upstream/wifi-libretiny-state-member' into integration

This commit is contained in:
J. Nick Koston
2026-03-29 18:48:36 -10:00
6 changed files with 62 additions and 73 deletions
+10 -22
View File
@@ -289,12 +289,12 @@ def final_validate(config):
def _consume_wifi_sockets(config: ConfigType) -> ConfigType:
"""Register UDP PCBs used internally by lwIP for DHCP and DNS.
Needed on LibreTiny and RP2040 where we directly set MEMP_NUM_UDP_PCB (the
raw PCB pool shared by both application sockets and lwIP internals like
DHCP/DNS). On ESP32, CONFIG_LWIP_MAX_SOCKETS only controls the POSIX socket
layer — DHCP/DNS use raw udp_new() which bypasses it entirely.
Only needed on LibreTiny where we directly set MEMP_NUM_UDP_PCB (the raw
PCB pool shared by both application sockets and lwIP internals like DHCP/DNS).
On ESP32, CONFIG_LWIP_MAX_SOCKETS only controls the POSIX socket layer —
DHCP/DNS use raw udp_new() which bypasses it entirely.
"""
if not (CORE.is_bk72xx or CORE.is_rtl87xx or CORE.is_ln882x or CORE.is_rp2040):
if not (CORE.is_bk72xx or CORE.is_rtl87xx or CORE.is_ln882x):
return config
from esphome.components import socket
@@ -454,25 +454,13 @@ def safe_ip(ip):
def manual_ip(config):
if config is None:
return None
fields = {
"static_ip": CONF_STATIC_IP,
"gateway": CONF_GATEWAY,
"subnet": CONF_SUBNET,
"dns1": CONF_DNS1,
"dns2": CONF_DNS2,
}
if CORE.is_esp8266:
# On ESP8266, .rodata is mapped to RAM. Using StructInitializer with all
# compile-time constant fields causes the compiler to place a const blob
# in .rodata, silently consuming ~20 bytes of RAM. Field-by-field assignment
# encodes the values as immediate operands in flash instructions instead.
cg.add(cg.RawExpression("wifi::ManualIP manual_ip{}"))
for member, key in fields.items():
cg.add(cg.RawExpression(f"manual_ip.{member} = {safe_ip(config.get(key))}"))
return cg.RawExpression("manual_ip")
return cg.StructInitializer(
ManualIP,
*((member, safe_ip(config.get(key))) for member, key in fields.items()),
("static_ip", safe_ip(config[CONF_STATIC_IP])),
("gateway", safe_ip(config[CONF_GATEWAY])),
("subnet", safe_ip(config[CONF_SUBNET])),
("dns1", safe_ip(config.get(CONF_DNS1))),
("dns2", safe_ip(config.get(CONF_DNS2))),
)
+4 -9
View File
@@ -726,7 +726,7 @@ void WiFiComponent::restart_adapter() {
// through start_connecting() first. Without this clear, stale errors would
// trigger spurious "failed (callback)" logs. The canonical clear location
// is in start_connecting(); this is the only exception to that pattern.
this->error_from_callback_ = 0;
this->error_from_callback_ = false;
}
void WiFiComponent::loop() {
@@ -784,7 +784,8 @@ void WiFiComponent::loop() {
}
case WIFI_COMPONENT_STATE_STA_CONNECTED: {
if (!this->is_connected_()) {
// Use cached connected_ set unconditionally at the top of loop()
if (!this->connected_) {
ESP_LOGW(TAG, "Connection lost; reconnecting");
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING;
this->retry_connect();
@@ -1148,7 +1149,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) {
// This is the canonical location for clearing the flag since all connection
// attempts go through start_connecting(). The only other clear is in
// restart_adapter() which enters COOLDOWN without calling start_connecting().
this->error_from_callback_ = 0;
this->error_from_callback_ = false;
if (!this->wifi_sta_connect_(ap)) {
ESP_LOGE(TAG, "wifi_sta_connect_ failed");
@@ -2000,7 +2001,6 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() {
(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);
}
char bssid_s[18];
format_mac_addr_upper(failed_bssid.value().data(), bssid_s);
ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid != nullptr ? ssid : "",
@@ -2130,11 +2130,6 @@ void WiFiComponent::retry_connect() {
}
void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
bool WiFiComponent::is_connected_() const {
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED &&
this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_;
}
void WiFiComponent::update_connected_state_() { this->connected_ = this->is_connected_(); }
void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) {
this->power_save_ = power_save;
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
+8 -2
View File
@@ -670,8 +670,11 @@ class WiFiComponent final : public Component {
bool wifi_sta_connect_(const WiFiAP &ap);
void wifi_pre_setup_();
WiFiSTAConnectStatus wifi_sta_connect_status_() const;
bool is_connected_() const;
void update_connected_state_();
bool is_connected_() const {
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED &&
this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_;
}
void update_connected_state_() { this->connected_ = this->is_connected_(); }
bool wifi_scan_start_(bool passive);
#ifdef USE_WIFI_AP
@@ -811,6 +814,9 @@ class WiFiComponent final : public Component {
uint8_t num_ipv6_addresses_{0};
#endif /* USE_NETWORK_IPV6 */
bool error_from_callback_{false};
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
uint8_t sta_state_{0}; // Platform-specific enum, defined in platform cpp file
#endif
RetryHiddenMode retry_hidden_mode_{RetryHiddenMode::BLIND_RETRY};
RoamingState roaming_state_{RoamingState::IDLE};
bssid_t roaming_target_bssid_{}; // BSSID of the AP we're trying to roam to
@@ -17,8 +17,7 @@ extern "C" {
#include "lwip/err.h"
#include "lwip/dns.h"
#include "lwip/dhcp.h"
#include "lwip/prot/dhcp.h" // DHCP_STATE_BOUND
#include "lwip/init.h" // LWIP_VERSION_
#include "lwip/init.h" // LWIP_VERSION_
#include "lwip/apps/sntp.h"
#include "lwip/netif.h" // struct netif
#include <AddrList.h>
@@ -45,11 +44,14 @@ namespace esphome::wifi {
static const char *const TAG = "wifi_esp8266";
static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
enum class ESP8266WiFiSTAState : uint8_t {
IDLE, // Not connecting
CONNECTING, // Connection in progress
ASSOCIATED, // Associated to AP, waiting for IP
CONNECTED, // Successfully connected with IP
ERROR_NOT_FOUND, // AP not found (probe failed)
ERROR_FAILED, // Connection failed (auth, timeout, etc.)
};
bool WiFiComponent::wifi_mode_(optional<bool> sta, optional<bool> ap) {
uint8_t current_mode = wifi_get_opmode();
@@ -360,11 +362,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
// Reset flags, do this _before_ wifi_station_connect as the callback method
// may be called from wifi_station_connect
s_sta_connecting = true;
s_sta_connected = false;
s_sta_got_ip = false;
s_sta_connect_error = false;
s_sta_connect_not_found = false;
this->sta_state_ = static_cast<uint8_t>(ESP8266WiFiSTAState::CONNECTING);
ETS_UART_INTR_DISABLE();
ret = wifi_station_connect();
@@ -494,7 +492,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid, bssid_buf,
it.channel);
#endif
s_sta_connected = true;
global_wifi_component->sta_state_ = static_cast<uint8_t>(ESP8266WiFiSTAState::ASSOCIATED);
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
// Defer listener notification until state machine reaches STA_CONNECTED
// This ensures wifi.connected condition returns true in listener automations
@@ -507,16 +505,14 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
if (it.reason == REASON_NO_AP_FOUND) {
ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
(const char *) it.ssid);
s_sta_connect_not_found = true;
global_wifi_component->sta_state_ = static_cast<uint8_t>(ESP8266WiFiSTAState::ERROR_NOT_FOUND);
} else {
char bssid_s[18];
format_mac_addr_upper(it.bssid, bssid_s);
ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
(const char *) it.ssid, bssid_s, LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
s_sta_connect_error = true;
global_wifi_component->sta_state_ = static_cast<uint8_t>(ESP8266WiFiSTAState::ERROR_FAILED);
}
s_sta_connected = false;
s_sta_connecting = false;
global_wifi_component->error_from_callback_ = true;
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
global_wifi_component->pending_.disconnect = true;
@@ -542,7 +538,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
mask_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", network::IPAddress(&it.ip).str_to(ip_buf),
network::IPAddress(&it.gw).str_to(gw_buf), network::IPAddress(&it.mask).str_to(mask_buf));
s_sta_got_ip = true;
global_wifi_component->sta_state_ = static_cast<uint8_t>(ESP8266WiFiSTAState::CONNECTED);
#ifdef USE_WIFI_IP_STATE_LISTENERS
// Defer listener callbacks to main loop - system context has limited stack
global_wifi_component->pending_.got_ip = true;
@@ -637,17 +633,22 @@ void WiFiComponent::wifi_pre_setup_() {
}
WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const {
station_status_t status = wifi_station_get_connect_status();
if (status == STATION_GOT_IP)
// Use cached state from wifi_event_callback() instead of calling
// wifi_station_get_connect_status() which queries the SDK every time.
// Use if statements with early returns instead of switch to avoid GCC
// generating a CSWTCH lookup table in .rodata (flash) on ESP8266.
auto state = static_cast<ESP8266WiFiSTAState>(this->sta_state_);
if (state == ESP8266WiFiSTAState::CONNECTED)
return WiFiSTAConnectStatus::CONNECTED;
if (status == STATION_NO_AP_FOUND)
if (state == ESP8266WiFiSTAState::ERROR_NOT_FOUND)
return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND;
if (status == STATION_CONNECT_FAIL || status == STATION_WRONG_PASSWORD)
if (state == ESP8266WiFiSTAState::ERROR_FAILED)
return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED;
if (status == STATION_CONNECTING)
if (state == ESP8266WiFiSTAState::CONNECTING || state == ESP8266WiFiSTAState::ASSOCIATED)
return WiFiSTAConnectStatus::CONNECTING;
return WiFiSTAConnectStatus::IDLE;
}
bool WiFiComponent::wifi_scan_start_(bool passive) {
// enable STA
if (!this->wifi_mode_(true, {}))
@@ -805,7 +805,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
}
s_sta_connected = false;
s_sta_connecting = false;
error_from_callback_ = 1;
error_from_callback_ = true;
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
this->notify_disconnect_state_listeners_();
#endif
@@ -97,8 +97,6 @@ enum class LTWiFiSTAState : uint8_t {
ERROR_FAILED, // Connection failed (auth, timeout, etc.)
};
static LTWiFiSTAState s_sta_state = LTWiFiSTAState::IDLE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Count of ignored disconnect events during connection - too many indicates real failure
static uint8_t s_ignored_disconnect_count = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Threshold for ignored disconnect events before treating as connection failure
@@ -223,7 +221,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
this->wifi_apply_hostname_();
// Reset state machine and disconnect counter before connecting
s_sta_state = LTWiFiSTAState::CONNECTING;
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::CONNECTING);
s_ignored_disconnect_count = 0;
WiFiStatus status = WiFi.begin(ap.ssid_.c_str(), ap.password_.empty() ? NULL : ap.password_.c_str(),
@@ -459,7 +457,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
}
case ESPHOME_EVENT_ID_WIFI_STA_STOP: {
ESP_LOGV(TAG, "STA stop");
s_sta_state = LTWiFiSTAState::IDLE;
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::IDLE);
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
@@ -479,7 +477,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
// For static IP configurations, GOT_IP event may not fire, so set connected state here
#ifdef USE_WIFI_MANUAL_IP
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
s_sta_state = LTWiFiSTAState::CONNECTED;
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::CONNECTED);
#ifdef USE_WIFI_IP_STATE_LISTENERS
this->notify_ip_state_listeners_();
#endif
@@ -501,14 +499,15 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
// Only ignore benign reasons - real failures like NO_AP_FOUND should still be processed.
// However, if we get too many of these events (IGNORED_DISCONNECT_THRESHOLD), treat it
// as a real connection failure to avoid waiting the full timeout for a failing connection.
if (it.ssid_len == 0 && s_sta_state == LTWiFiSTAState::CONNECTING && it.reason != WIFI_REASON_NO_AP_FOUND) {
if (it.ssid_len == 0 && this->sta_state_ == static_cast<uint8_t>(LTWiFiSTAState::CONNECTING) &&
it.reason != WIFI_REASON_NO_AP_FOUND) {
s_ignored_disconnect_count++;
if (s_ignored_disconnect_count >= IGNORED_DISCONNECT_THRESHOLD) {
ESP_LOGW(TAG, "Too many disconnect events (%u) while connecting, treating as failure (reason=%s)",
s_ignored_disconnect_count, get_disconnect_reason_str(it.reason));
s_sta_state = LTWiFiSTAState::ERROR_FAILED;
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::ERROR_FAILED);
WiFi.disconnect();
this->error_from_callback_ = 1;
this->error_from_callback_ = true;
// Don't break - fall through to notify listeners
} else {
ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s, count=%u)",
@@ -520,13 +519,13 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
if (it.reason == WIFI_REASON_NO_AP_FOUND) {
ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
(const char *) it.ssid);
s_sta_state = LTWiFiSTAState::ERROR_NOT_FOUND;
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::ERROR_NOT_FOUND);
} else {
char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(it.bssid, bssid_s);
ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
(const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason));
s_sta_state = LTWiFiSTAState::ERROR_FAILED;
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::ERROR_FAILED);
}
uint8_t reason = it.reason;
@@ -534,7 +533,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
reason == WIFI_REASON_NO_AP_FOUND || reason == WIFI_REASON_ASSOC_FAIL ||
reason == WIFI_REASON_HANDSHAKE_TIMEOUT) {
WiFi.disconnect();
this->error_from_callback_ = 1;
this->error_from_callback_ = true;
}
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
@@ -550,8 +549,8 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
if (it.old_mode != WIFI_AUTH_OPEN && it.new_mode == WIFI_AUTH_OPEN) {
ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting");
WiFi.disconnect();
this->error_from_callback_ = 1;
s_sta_state = LTWiFiSTAState::ERROR_FAILED;
this->error_from_callback_ = true;
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::ERROR_FAILED);
}
break;
}
@@ -559,7 +558,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGV(TAG, "static_ip=%s gateway=%s", network::IPAddress(WiFi.localIP()).str_to(ip_buf),
network::IPAddress(WiFi.gatewayIP()).str_to(gw_buf));
s_sta_state = LTWiFiSTAState::CONNECTED;
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::CONNECTED);
#ifdef USE_WIFI_IP_STATE_LISTENERS
this->notify_ip_state_listeners_();
#endif
@@ -637,7 +636,7 @@ void WiFiComponent::wifi_pre_setup_() {
WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const {
// Use state machine instead of querying WiFi.status() directly
// State is updated in main loop from queued events, ensuring thread safety
switch (s_sta_state) {
switch (static_cast<LTWiFiSTAState>(this->sta_state_)) {
case LTWiFiSTAState::CONNECTED:
return WiFiSTAConnectStatus::CONNECTED;
case LTWiFiSTAState::ERROR_NOT_FOUND:
@@ -758,7 +757,7 @@ network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()};
bool WiFiComponent::wifi_disconnect_() {
// Reset state first so disconnect events aren't ignored
// and wifi_sta_connect_status_() returns IDLE instead of CONNECTING
s_sta_state = LTWiFiSTAState::IDLE;
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::IDLE);
return WiFi.disconnect();
}