From 0e3e060fce2f28fd9435f3e0d40677cead26af0a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 6 Feb 2026 16:49:24 +0100 Subject: [PATCH] [wifi] Avoid jump tables in LOG_STR switch statements to save ESP8266 RAM --- esphome/components/wifi/wifi_component.cpp | 30 ++--- .../wifi/wifi_component_esp8266.cpp | 126 ++++++++---------- 2 files changed, 72 insertions(+), 84 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e9b78c9225..bc807e7bb8 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -236,25 +236,23 @@ static const char *const TAG = "wifi"; /// │ - Roaming fail (RECONNECTING→IDLE): counter preserved (ping-pong) │ /// └──────────────────────────────────────────────────────────────────────┘ +// Use if-chain instead of switch to avoid jump table in RODATA (wastes RAM on ESP8266) static const LogString *retry_phase_to_log_string(WiFiRetryPhase phase) { - switch (phase) { - case WiFiRetryPhase::INITIAL_CONNECT: - return LOG_STR("INITIAL_CONNECT"); + if (phase == WiFiRetryPhase::INITIAL_CONNECT) + return LOG_STR("INITIAL_CONNECT"); #ifdef USE_WIFI_FAST_CONNECT - case WiFiRetryPhase::FAST_CONNECT_CYCLING_APS: - return LOG_STR("FAST_CONNECT_CYCLING"); + if (phase == WiFiRetryPhase::FAST_CONNECT_CYCLING_APS) + return LOG_STR("FAST_CONNECT_CYCLING"); #endif - case WiFiRetryPhase::EXPLICIT_HIDDEN: - return LOG_STR("EXPLICIT_HIDDEN"); - case WiFiRetryPhase::SCAN_CONNECTING: - return LOG_STR("SCAN_CONNECTING"); - case WiFiRetryPhase::RETRY_HIDDEN: - return LOG_STR("RETRY_HIDDEN"); - case WiFiRetryPhase::RESTARTING_ADAPTER: - return LOG_STR("RESTARTING"); - default: - return LOG_STR("UNKNOWN"); - } + if (phase == WiFiRetryPhase::EXPLICIT_HIDDEN) + return LOG_STR("EXPLICIT_HIDDEN"); + if (phase == WiFiRetryPhase::SCAN_CONNECTING) + return LOG_STR("SCAN_CONNECTING"); + if (phase == WiFiRetryPhase::RETRY_HIDDEN) + return LOG_STR("RETRY_HIDDEN"); + if (phase == WiFiRetryPhase::RESTARTING_ADAPTER) + return LOG_STR("RESTARTING"); + return LOG_STR("UNKNOWN"); } bool WiFiComponent::went_through_explicit_hidden_phase_() const { diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 0765fdc03b..6c3f71b9b7 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -416,75 +416,65 @@ PROGMEM_STRING_TABLE(OpModeStrings, "OFF", "STA", "AP", "AP+STA", "UNKNOWN"); const LogString *get_op_mode_str(uint8_t mode) { return OpModeStrings::get_log_str(mode, OpModeStrings::LAST_INDEX); } +// Use if-chain instead of switch to avoid jump tables in RODATA (wastes RAM on ESP8266). +// A single switch would generate a sparse lookup table with ~175 default entries, wasting 700 bytes of RAM. +// Even split switches still generate smaller jump tables in RODATA. const LogString *get_disconnect_reason_str(uint8_t reason) { - /* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the - * REASON_* constants aren't continuous, and GCC will fill in the gap with the default value -- wasting 4 bytes of RAM - * per entry. As there's ~175 default entries, this wastes 700 bytes of RAM. - */ - if (reason <= REASON_CIPHER_SUITE_REJECTED) { // This must be the last constant with a value <200 - switch (reason) { - case REASON_AUTH_EXPIRE: - return LOG_STR("Auth Expired"); - case REASON_AUTH_LEAVE: - return LOG_STR("Auth Leave"); - case REASON_ASSOC_EXPIRE: - return LOG_STR("Association Expired"); - case REASON_ASSOC_TOOMANY: - return LOG_STR("Too Many Associations"); - case REASON_NOT_AUTHED: - return LOG_STR("Not Authenticated"); - case REASON_NOT_ASSOCED: - return LOG_STR("Not Associated"); - case REASON_ASSOC_LEAVE: - return LOG_STR("Association Leave"); - case REASON_ASSOC_NOT_AUTHED: - return LOG_STR("Association not Authenticated"); - case REASON_DISASSOC_PWRCAP_BAD: - return LOG_STR("Disassociate Power Cap Bad"); - case REASON_DISASSOC_SUPCHAN_BAD: - return LOG_STR("Disassociate Supported Channel Bad"); - case REASON_IE_INVALID: - return LOG_STR("IE Invalid"); - case REASON_MIC_FAILURE: - return LOG_STR("Mic Failure"); - case REASON_4WAY_HANDSHAKE_TIMEOUT: - return LOG_STR("4-Way Handshake Timeout"); - case REASON_GROUP_KEY_UPDATE_TIMEOUT: - return LOG_STR("Group Key Update Timeout"); - case REASON_IE_IN_4WAY_DIFFERS: - return LOG_STR("IE In 4-Way Handshake Differs"); - case REASON_GROUP_CIPHER_INVALID: - return LOG_STR("Group Cipher Invalid"); - case REASON_PAIRWISE_CIPHER_INVALID: - return LOG_STR("Pairwise Cipher Invalid"); - case REASON_AKMP_INVALID: - return LOG_STR("AKMP Invalid"); - case REASON_UNSUPP_RSN_IE_VERSION: - return LOG_STR("Unsupported RSN IE version"); - case REASON_INVALID_RSN_IE_CAP: - return LOG_STR("Invalid RSN IE Cap"); - case REASON_802_1X_AUTH_FAILED: - return LOG_STR("802.1x Authentication Failed"); - case REASON_CIPHER_SUITE_REJECTED: - return LOG_STR("Cipher Suite Rejected"); - } - } - - switch (reason) { - case REASON_BEACON_TIMEOUT: - return LOG_STR("Beacon Timeout"); - case REASON_NO_AP_FOUND: - return LOG_STR("AP Not Found"); - case REASON_AUTH_FAIL: - return LOG_STR("Authentication Failed"); - case REASON_ASSOC_FAIL: - return LOG_STR("Association Failed"); - case REASON_HANDSHAKE_TIMEOUT: - return LOG_STR("Handshake Failed"); - case REASON_UNSPECIFIED: - default: - return LOG_STR("Unspecified"); - } + if (reason == REASON_AUTH_EXPIRE) + return LOG_STR("Auth Expired"); + if (reason == REASON_AUTH_LEAVE) + return LOG_STR("Auth Leave"); + if (reason == REASON_ASSOC_EXPIRE) + return LOG_STR("Association Expired"); + if (reason == REASON_ASSOC_TOOMANY) + return LOG_STR("Too Many Associations"); + if (reason == REASON_NOT_AUTHED) + return LOG_STR("Not Authenticated"); + if (reason == REASON_NOT_ASSOCED) + return LOG_STR("Not Associated"); + if (reason == REASON_ASSOC_LEAVE) + return LOG_STR("Association Leave"); + if (reason == REASON_ASSOC_NOT_AUTHED) + return LOG_STR("Association not Authenticated"); + if (reason == REASON_DISASSOC_PWRCAP_BAD) + return LOG_STR("Disassociate Power Cap Bad"); + if (reason == REASON_DISASSOC_SUPCHAN_BAD) + return LOG_STR("Disassociate Supported Channel Bad"); + if (reason == REASON_IE_INVALID) + return LOG_STR("IE Invalid"); + if (reason == REASON_MIC_FAILURE) + return LOG_STR("Mic Failure"); + if (reason == REASON_4WAY_HANDSHAKE_TIMEOUT) + return LOG_STR("4-Way Handshake Timeout"); + if (reason == REASON_GROUP_KEY_UPDATE_TIMEOUT) + return LOG_STR("Group Key Update Timeout"); + if (reason == REASON_IE_IN_4WAY_DIFFERS) + return LOG_STR("IE In 4-Way Handshake Differs"); + if (reason == REASON_GROUP_CIPHER_INVALID) + return LOG_STR("Group Cipher Invalid"); + if (reason == REASON_PAIRWISE_CIPHER_INVALID) + return LOG_STR("Pairwise Cipher Invalid"); + if (reason == REASON_AKMP_INVALID) + return LOG_STR("AKMP Invalid"); + if (reason == REASON_UNSUPP_RSN_IE_VERSION) + return LOG_STR("Unsupported RSN IE version"); + if (reason == REASON_INVALID_RSN_IE_CAP) + return LOG_STR("Invalid RSN IE Cap"); + if (reason == REASON_802_1X_AUTH_FAILED) + return LOG_STR("802.1x Authentication Failed"); + if (reason == REASON_CIPHER_SUITE_REJECTED) + return LOG_STR("Cipher Suite Rejected"); + if (reason == REASON_BEACON_TIMEOUT) + return LOG_STR("Beacon Timeout"); + if (reason == REASON_NO_AP_FOUND) + return LOG_STR("AP Not Found"); + if (reason == REASON_AUTH_FAIL) + return LOG_STR("Authentication Failed"); + if (reason == REASON_ASSOC_FAIL) + return LOG_STR("Association Failed"); + if (reason == REASON_HANDSHAKE_TIMEOUT) + return LOG_STR("Handshake Failed"); + return LOG_STR("Unspecified"); } // TODO: This callback runs in ESP8266 system context with limited stack (~2KB).