From 5917444e9fd1979ccaccaedd0545ef106b0bad0d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 27 Oct 2025 20:55:32 -0500 Subject: [PATCH] [wifi] Store credentials in PROGMEM on ESP8266 to reduce RAM usage --- esphome/components/wifi/__init__.py | 26 +++++++-- esphome/components/wifi/wifi_component.cpp | 57 +++++++++++++++++-- esphome/components/wifi/wifi_component.h | 22 +++++-- .../wifi/wifi_component_esp8266.cpp | 37 ++++++++---- 4 files changed, 120 insertions(+), 22 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index b980bab4aa..610b23d58b 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -140,17 +140,21 @@ EAP_AUTH_SCHEMA = cv.All( cv.has_at_least_one_key(CONF_IDENTITY, CONF_CERTIFICATE), ) +CONF_AP_TIMEOUT = "ap_timeout" +CONF_SSID_DATA_ID = "ssid_data_id" +CONF_PASSWORD_DATA_ID = "password_data_id" + WIFI_NETWORK_BASE = cv.Schema( { cv.GenerateID(): cv.declare_id(WiFiAP), + cv.GenerateID(CONF_SSID_DATA_ID): cv.declare_id(cg.uint8), + cv.GenerateID(CONF_PASSWORD_DATA_ID): cv.declare_id(cg.uint8), cv.Optional(CONF_SSID): cv.ssid, cv.Optional(CONF_PASSWORD): validate_password, cv.Optional(CONF_CHANNEL): validate_channel, cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, } ) - -CONF_AP_TIMEOUT = "ap_timeout" WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend( { cv.Optional( @@ -352,9 +356,23 @@ def manual_ip(config): def wifi_network(config, ap, static_ip): if CONF_SSID in config: - cg.add(ap.set_ssid(config[CONF_SSID])) + ssid = config[CONF_SSID] + if CORE.is_esp8266: + # On ESP8266, store SSID in PROGMEM to save RAM + prog_arr = cg.progmem_array(config[CONF_SSID_DATA_ID], ssid) + cg.add(ap.set_ssid_flash(prog_arr)) + else: + # On ESP32/RP2040, string literals are already in rodata (flash) + cg.add(ap.set_ssid_flash(ssid)) if CONF_PASSWORD in config: - cg.add(ap.set_password(config[CONF_PASSWORD])) + password = config[CONF_PASSWORD] + if CORE.is_esp8266: + # On ESP8266, store password in PROGMEM to save RAM + prog_arr = cg.progmem_array(config[CONF_PASSWORD_DATA_ID], password) + cg.add(ap.set_password_flash(prog_arr)) + else: + # On ESP32/RP2040, string literals are already in rodata (flash) + cg.add(ap.set_password_flash(password)) if CONF_EAP in config: cg.add(ap.set_eap(eap_auth(config[CONF_EAP]))) cg.add_define("USE_WIFI_WPA2_EAP") diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index b278e5a386..54e9c842cd 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -888,19 +888,68 @@ void WiFiComponent::save_fast_connect_settings_() { } #endif -void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; } +void WiFiAP::set_ssid(const std::string &ssid) { + this->ssid_storage_ = ssid; + this->ssid_ = this->ssid_storage_->c_str(); +} void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; } void WiFiAP::set_bssid(optional bssid) { this->bssid_ = bssid; } -void WiFiAP::set_password(const std::string &password) { this->password_ = password; } +void WiFiAP::set_password(const std::string &password) { + this->password_storage_ = password; + this->password_ = this->password_storage_->c_str(); +} +#ifdef USE_ESP8266 +void WiFiAP::set_ssid_flash(const uint8_t *ssid) { + this->ssid_storage_.reset(); + this->ssid_ = reinterpret_cast(ssid); +} +void WiFiAP::set_password_flash(const uint8_t *password) { + this->password_storage_.reset(); + this->password_ = reinterpret_cast(password); +} +#else +void WiFiAP::set_ssid_flash(const char *ssid) { + this->ssid_storage_.reset(); + this->ssid_ = ssid; +} +void WiFiAP::set_password_flash(const char *password) { + this->password_storage_.reset(); + this->password_ = password; +} +#endif #ifdef USE_WIFI_WPA2_EAP void WiFiAP::set_eap(optional eap_auth) { this->eap_ = std::move(eap_auth); } #endif void WiFiAP::set_channel(optional channel) { this->channel_ = channel; } void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = manual_ip; } void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; } -const std::string &WiFiAP::get_ssid() const { return this->ssid_; } +std::string WiFiAP::get_ssid() const { +#ifdef USE_ESP8266 + if (!this->ssid_storage_.has_value()) { + // Flash storage - read from PROGMEM + size_t len = strlen_P(this->ssid_); + std::string result; + result.resize(len); + memcpy_P(&result[0], this->ssid_, len); + return result; + } +#endif + return std::string(this->ssid_); +} const optional &WiFiAP::get_bssid() const { return this->bssid_; } -const std::string &WiFiAP::get_password() const { return this->password_; } +std::string WiFiAP::get_password() const { +#ifdef USE_ESP8266 + if (!this->password_storage_.has_value()) { + // Flash storage - read from PROGMEM + size_t len = strlen_P(this->password_); + std::string result; + result.resize(len); + memcpy_P(&result[0], this->password_, len); + return result; + } +#endif + return std::string(this->password_); +} #ifdef USE_WIFI_WPA2_EAP const optional &WiFiAP::get_eap() const { return this->eap_; } #endif diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 42f78dbfac..3b1271cfbb 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -135,6 +135,18 @@ class WiFiAP { void set_bssid(bssid_t bssid); void set_bssid(optional bssid); void set_password(const std::string &password); + /// Set SSID from flash string (PROGMEM on ESP8266, rodata on ESP32) +#ifdef USE_ESP8266 + void set_ssid_flash(const uint8_t *ssid); +#else + void set_ssid_flash(const char *ssid); +#endif + /// Set password from flash string (PROGMEM on ESP8266, rodata on ESP32) +#ifdef USE_ESP8266 + void set_password_flash(const uint8_t *password); +#else + void set_password_flash(const char *password); +#endif #ifdef USE_WIFI_WPA2_EAP void set_eap(optional eap_auth); #endif // USE_WIFI_WPA2_EAP @@ -142,9 +154,9 @@ class WiFiAP { void set_priority(float priority) { priority_ = priority; } void set_manual_ip(optional manual_ip); void set_hidden(bool hidden); - const std::string &get_ssid() const; + std::string get_ssid() const; const optional &get_bssid() const; - const std::string &get_password() const; + std::string get_password() const; #ifdef USE_WIFI_WPA2_EAP const optional &get_eap() const; #endif // USE_WIFI_WPA2_EAP @@ -154,8 +166,10 @@ class WiFiAP { bool get_hidden() const; protected: - std::string ssid_; - std::string password_; + const char *ssid_{nullptr}; + const char *password_{nullptr}; + optional ssid_storage_; + optional password_storage_; optional bssid_; #ifdef USE_WIFI_WPA2_EAP optional eap_; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 4e17c42f41..0ee9b7faad 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -236,16 +236,25 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { struct station_config conf {}; memset(&conf, 0, sizeof(conf)); - if (ap.get_ssid().size() > sizeof(conf.ssid)) { + + std::string ssid = ap.get_ssid(); + std::string password = ap.get_password(); + + size_t ssid_len = ssid.length(); + size_t password_len = password.length(); + + if (ssid_len > sizeof(conf.ssid)) { ESP_LOGE(TAG, "SSID too long"); return false; } - if (ap.get_password().size() > sizeof(conf.password)) { + + if (password_len > sizeof(conf.password)) { ESP_LOGE(TAG, "Password too long"); return false; } - memcpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); - memcpy(reinterpret_cast(conf.password), ap.get_password().c_str(), ap.get_password().size()); + + memcpy(conf.ssid, ssid.c_str(), ssid_len); + memcpy(conf.password, password.c_str(), password_len); if (ap.get_bssid().has_value()) { conf.bssid_set = 1; @@ -791,27 +800,35 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; struct softap_config conf {}; - if (ap.get_ssid().size() > sizeof(conf.ssid)) { + + std::string ssid = ap.get_ssid(); + std::string password = ap.get_password(); + + size_t ssid_len = ssid.length(); + size_t password_len = password.length(); + + if (ssid_len > sizeof(conf.ssid)) { ESP_LOGE(TAG, "AP SSID too long"); return false; } - memcpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); - conf.ssid_len = static_cast(ap.get_ssid().size()); + + memcpy(conf.ssid, ssid.c_str(), ssid_len); + conf.ssid_len = static_cast(ssid_len); conf.channel = ap.get_channel().value_or(1); conf.ssid_hidden = ap.get_hidden(); conf.max_connection = 5; conf.beacon_interval = 100; - if (ap.get_password().empty()) { + if (password_len == 0) { conf.authmode = AUTH_OPEN; *conf.password = 0; } else { conf.authmode = AUTH_WPA2_PSK; - if (ap.get_password().size() > sizeof(conf.password)) { + if (password_len > sizeof(conf.password)) { ESP_LOGE(TAG, "AP password too long"); return false; } - memcpy(reinterpret_cast(conf.password), ap.get_password().c_str(), ap.get_password().size()); + memcpy(conf.password, password.c_str(), password_len); } ETS_UART_INTR_DISABLE();