From e12ed08487a310860ba9ea39b7b75b99cb69ceca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 15:24:24 -0600 Subject: [PATCH] [wifi] Add CompactString to reduce WiFi scan heap fragmentation (#13472) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../esp32_improv/esp32_improv_component.cpp | 4 +- .../improv_serial/improv_serial_component.cpp | 20 ++- esphome/components/wifi/wifi_component.cpp | 144 +++++++++++++----- esphome/components/wifi/wifi_component.h | 76 ++++++++- .../wifi/wifi_component_esp8266.cpp | 26 ++-- .../wifi/wifi_component_esp_idf.cpp | 25 ++- .../wifi/wifi_component_libretiny.cpp | 8 +- .../components/wifi/wifi_component_pico_w.cpp | 9 +- .../wifi_info/wifi_info_text_sensor.cpp | 2 +- 9 files changed, 225 insertions(+), 89 deletions(-) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 1a19472c87..83bc842a3d 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -338,8 +338,8 @@ void ESP32ImprovComponent::process_incoming_data_() { return; } wifi::WiFiAP sta{}; - sta.set_ssid(command.ssid); - sta.set_password(command.password); + sta.set_ssid(command.ssid.c_str()); + sta.set_password(command.password.c_str()); this->connecting_sta_ = sta; wifi::global_wifi_component->set_sta(sta); diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index b4d9943955..edceb9a3b1 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -235,8 +235,8 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command switch (command.command) { case improv::WIFI_SETTINGS: { wifi::WiFiAP sta{}; - sta.set_ssid(command.ssid); - sta.set_password(command.password); + sta.set_ssid(command.ssid.c_str()); + sta.set_password(command.password.c_str()); this->connecting_sta_ = sta; wifi::global_wifi_component->set_sta(sta); @@ -267,16 +267,26 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command for (auto &scan : results) { if (scan.get_is_hidden()) continue; - const std::string &ssid = scan.get_ssid(); - if (std::find(networks.begin(), networks.end(), ssid) != networks.end()) + const char *ssid_cstr = scan.get_ssid().c_str(); + // Check if we've already sent this SSID + bool duplicate = false; + for (const auto &seen : networks) { + if (strcmp(seen.c_str(), ssid_cstr) == 0) { + duplicate = true; + break; + } + } + if (duplicate) continue; + // Only allocate std::string after confirming it's not a duplicate + std::string ssid(ssid_cstr); // Send each ssid separately to avoid overflowing the buffer char rssi_buf[5]; // int8_t: -128 to 127, max 4 chars + null *int8_to_str(rssi_buf, scan.get_rssi()) = '\0'; std::vector data = improv::build_rpc_response(improv::GET_WIFI_NETWORKS, {ssid, rssi_buf, YESNO(scan.get_with_auth())}, false); this->send_response_(data); - networks.push_back(ssid); + networks.push_back(std::move(ssid)); } // Send empty response to signify the end of the list. std::vector data = diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e350f990af..61d05d7635 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -20,6 +20,7 @@ #endif #include +#include #include #include "lwip/dns.h" #include "lwip/err.h" @@ -47,6 +48,69 @@ namespace esphome::wifi { static const char *const TAG = "wifi"; +// CompactString implementation +CompactString::CompactString(const char *str, size_t len) { + if (len > MAX_LENGTH) { + len = MAX_LENGTH; // Clamp to max valid length + } + + this->length_ = len; + if (len <= INLINE_CAPACITY) { + // Store inline with null terminator + this->is_heap_ = 0; + if (len > 0) { + std::memcpy(this->storage_, str, len); + } + this->storage_[len] = '\0'; + } else { + // Heap allocate with null terminator + this->is_heap_ = 1; + char *heap_data = new char[len + 1]; // NOLINT(cppcoreguidelines-owning-memory) + std::memcpy(heap_data, str, len); + heap_data[len] = '\0'; + this->set_heap_ptr_(heap_data); + } +} + +CompactString::CompactString(const CompactString &other) : CompactString(other.data(), other.size()) {} + +CompactString &CompactString::operator=(const CompactString &other) { + if (this != &other) { + this->~CompactString(); + new (this) CompactString(other); + } + return *this; +} + +CompactString::CompactString(CompactString &&other) noexcept : length_(other.length_), is_heap_(other.is_heap_) { + // Copy full storage (includes null terminator for inline, or pointer for heap) + std::memcpy(this->storage_, other.storage_, INLINE_CAPACITY + 1); + other.length_ = 0; + other.is_heap_ = 0; + other.storage_[0] = '\0'; +} + +CompactString &CompactString::operator=(CompactString &&other) noexcept { + if (this != &other) { + this->~CompactString(); + new (this) CompactString(std::move(other)); + } + return *this; +} + +CompactString::~CompactString() { + if (this->is_heap_) { + delete[] this->get_heap_ptr_(); // NOLINT(cppcoreguidelines-owning-memory) + } +} + +bool CompactString::operator==(const CompactString &other) const { + return this->size() == other.size() && std::memcmp(this->data(), other.data(), this->size()) == 0; +} +bool CompactString::operator==(const StringRef &other) const { + return this->size() == other.size() && std::memcmp(this->data(), other.c_str(), this->size()) == 0; +} + /// WiFi Retry Logic - Priority-Based BSSID Selection /// /// The WiFi component uses a state machine with priority degradation to handle connection failures @@ -349,18 +413,18 @@ bool WiFiComponent::needs_scan_results_() const { return this->scan_result_.empty() || !this->scan_result_[0].get_matches(); } -bool WiFiComponent::ssid_was_seen_in_scan_(const std::string &ssid) const { +bool WiFiComponent::ssid_was_seen_in_scan_(const CompactString &ssid) const { // Check if this SSID is configured as hidden // If explicitly marked hidden, we should always try hidden mode regardless of scan results for (const auto &conf : this->sta_) { - if (conf.get_ssid() == ssid && conf.get_hidden()) { + if (conf.ssid_ == ssid && conf.get_hidden()) { return false; // Treat as not seen - force hidden mode attempt } } // Otherwise, check if we saw it in scan results for (const auto &scan : this->scan_result_) { - if (scan.get_ssid() == ssid) { + if (scan.ssid_ == ssid) { return true; } } @@ -409,14 +473,14 @@ bool WiFiComponent::matches_configured_network_(const char *ssid, const uint8_t continue; } // For BSSID-only configs (empty SSID), match by BSSID - if (sta.get_ssid().empty()) { + if (sta.ssid_.empty()) { if (sta.has_bssid() && std::memcmp(sta.get_bssid().data(), bssid, 6) == 0) { return true; } continue; } // Match by SSID - if (sta.get_ssid() == ssid) { + if (sta.ssid_ == ssid) { return true; } } @@ -465,18 +529,18 @@ int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index) { 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(i) < first_non_hidden_idx) { - ESP_LOGD(TAG, "Skipping " LOG_SECRET("'%s'") " (explicit hidden, already tried)", sta.get_ssid().c_str()); + ESP_LOGD(TAG, "Skipping " LOG_SECRET("'%s'") " (explicit hidden, already tried)", sta.ssid_.c_str()); continue; } } // In BLIND_RETRY mode, treat all networks as candidates // In SCAN_BASED mode, only retry networks that weren't seen in the scan - if (this->retry_hidden_mode_ == RetryHiddenMode::BLIND_RETRY || !this->ssid_was_seen_in_scan_(sta.get_ssid())) { - ESP_LOGD(TAG, "Hidden candidate " LOG_SECRET("'%s'") " at index %d", sta.get_ssid().c_str(), static_cast(i)); + if (this->retry_hidden_mode_ == RetryHiddenMode::BLIND_RETRY || !this->ssid_was_seen_in_scan_(sta.ssid_)) { + ESP_LOGD(TAG, "Hidden candidate " LOG_SECRET("'%s'") " at index %d", sta.ssid_.c_str(), static_cast(i)); return static_cast(i); } - ESP_LOGD(TAG, "Skipping hidden retry for visible network " LOG_SECRET("'%s'"), sta.get_ssid().c_str()); + ESP_LOGD(TAG, "Skipping hidden retry for visible network " LOG_SECRET("'%s'"), sta.ssid_.c_str()); } // No hidden SSIDs found return -1; @@ -593,11 +657,11 @@ void WiFiComponent::start() { // Fast connect optimization: only use when we have saved BSSID+channel data // 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()); + ESP_LOGI(TAG, "Starting fast_connect (saved) " LOG_SECRET("'%s'"), params.ssid_.c_str()); 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()); + ESP_LOGI(TAG, "Starting fast_connect (config) " LOG_SECRET("'%s'"), this->sta_[0].ssid_.c_str()); this->selected_sta_index_ = 0; params = this->build_params_for_current_phase_(); this->start_connecting(params); @@ -827,7 +891,7 @@ void WiFiComponent::setup_ap_config_() { if (this->ap_setup_) return; - if (this->ap_.get_ssid().empty()) { + if (this->ap_.ssid_.empty()) { // Build AP SSID from app name without heap allocation // WiFi SSID max is 32 bytes, with MAC suffix we keep first 25 + last 7 static constexpr size_t AP_SSID_MAX_LEN = 32; @@ -863,7 +927,7 @@ void WiFiComponent::setup_ap_config_() { " AP SSID: '%s'\n" " AP Password: '%s'\n" " IP Address: %s", - this->ap_.get_ssid().c_str(), this->ap_.get_password().c_str(), this->wifi_soft_ap_ip().str_to(ip_buf)); + this->ap_.ssid_.c_str(), this->ap_.password_.c_str(), this->wifi_soft_ap_ip().str_to(ip_buf)); #ifdef USE_WIFI_MANUAL_IP auto manual_ip = this->ap_.get_manual_ip(); @@ -960,9 +1024,12 @@ WiFiAP WiFiComponent::get_sta() const { return config ? *config : WiFiAP{}; } void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &password) { + this->save_wifi_sta(ssid.c_str(), password.c_str()); +} +void WiFiComponent::save_wifi_sta(const char *ssid, const char *password) { SavedWifiSettings save{}; // zero-initialized - all bytes set to \0, guaranteeing null termination - strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid) - 1); // max 32 chars, byte 32 remains \0 - strncpy(save.password, password.c_str(), sizeof(save.password) - 1); // max 64 chars, byte 64 remains \0 + strncpy(save.ssid, ssid, sizeof(save.ssid) - 1); // max 32 chars, byte 32 remains \0 + strncpy(save.password, password, sizeof(save.password) - 1); // max 64 chars, byte 64 remains \0 this->pref_.save(&save); // ensure it's written immediately global_preferences->sync(); @@ -996,14 +1063,14 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { ESP_LOGI(TAG, "Connecting to " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") " (priority %d, attempt %u/%u in phase %s)...", - ap.get_ssid().c_str(), ap.has_bssid() ? bssid_s : LOG_STR_LITERAL("any"), priority, this->num_retried_ + 1, + ap.ssid_.c_str(), ap.has_bssid() ? bssid_s : 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_))); #ifdef ESPHOME_LOG_HAS_VERBOSE ESP_LOGV(TAG, "Connection Params:\n" " SSID: '%s'", - ap.get_ssid().c_str()); + ap.ssid_.c_str()); if (ap.has_bssid()) { ESP_LOGV(TAG, " BSSID: %s", bssid_s); } else { @@ -1036,7 +1103,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { client_key_present ? "present" : "not present"); } else { #endif - ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), ap.get_password().c_str()); + ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), ap.password_.c_str()); #ifdef USE_WIFI_WPA2_EAP } #endif @@ -1411,7 +1478,7 @@ void WiFiComponent::check_connecting_finished(uint32_t now) { if (const WiFiAP *config = this->get_selected_sta_(); this->retry_phase_ == WiFiRetryPhase::RETRY_HIDDEN && config && !config->get_hidden() && this->scan_result_.empty()) { - ESP_LOGW(TAG, LOG_SECRET("'%s'") " should be marked hidden", config->get_ssid().c_str()); + ESP_LOGW(TAG, LOG_SECRET("'%s'") " should be marked hidden", config->ssid_.c_str()); } // Reset to initial phase on successful connection (don't log transition, just reset state) this->retry_phase_ = WiFiRetryPhase::INITIAL_CONNECT; @@ -1825,11 +1892,11 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { } // Get SSID for logging (use pointer to avoid copy) - const std::string *ssid = nullptr; + const char *ssid = nullptr; if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) { - ssid = &this->scan_result_[0].get_ssid(); + ssid = this->scan_result_[0].ssid_.c_str(); } else if (const WiFiAP *config = this->get_selected_sta_()) { - ssid = &config->get_ssid(); + ssid = config->ssid_.c_str(); } // Only decrease priority on the last attempt for this phase @@ -1849,8 +1916,8 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { } 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->c_str() : "", bssid_s, old_priority, new_priority); + ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid != nullptr ? ssid : "", + bssid_s, 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 @@ -2098,10 +2165,14 @@ 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_ = CompactString(ssid.c_str(), ssid.size()); } +void WiFiAP::set_ssid(const char *ssid) { this->ssid_ = CompactString(ssid, strlen(ssid)); } void WiFiAP::set_bssid(const bssid_t &bssid) { this->bssid_ = bssid; } void WiFiAP::clear_bssid() { this->bssid_ = {}; } -void WiFiAP::set_password(const std::string &password) { this->password_ = password; } +void WiFiAP::set_password(const std::string &password) { + this->password_ = CompactString(password.c_str(), password.size()); +} +void WiFiAP::set_password(const char *password) { this->password_ = CompactString(password, strlen(password)); } #ifdef USE_WIFI_WPA2_EAP void WiFiAP::set_eap(optional eap_auth) { this->eap_ = std::move(eap_auth); } #endif @@ -2111,10 +2182,8 @@ void WiFiAP::clear_channel() { this->channel_ = 0; } void WiFiAP::set_manual_ip(optional 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 bssid_t &WiFiAP::get_bssid() const { return this->bssid_; } bool WiFiAP::has_bssid() const { return this->bssid_ != bssid_t{}; } -const std::string &WiFiAP::get_password() const { return this->password_; } #ifdef USE_WIFI_WPA2_EAP const optional &WiFiAP::get_eap() const { return this->eap_; } #endif @@ -2125,12 +2194,12 @@ const optional &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) +WiFiScanResult::WiFiScanResult(const bssid_t &bssid, const char *ssid, size_t ssid_len, uint8_t channel, int8_t rssi, + bool with_auth, bool is_hidden) : bssid_(bssid), channel_(channel), rssi_(rssi), - ssid_(std::move(ssid)), + ssid_(ssid, ssid_len), with_auth_(with_auth), is_hidden_(is_hidden) {} bool WiFiScanResult::matches(const WiFiAP &config) const { @@ -2139,9 +2208,9 @@ bool WiFiScanResult::matches(const WiFiAP &config) const { // don't match SSID if (!this->is_hidden_) return false; - } else if (!config.get_ssid().empty()) { + } else if (!config.ssid_.empty()) { // check if SSID matches - if (config.get_ssid() != this->ssid_) + if (this->ssid_ != config.ssid_) return false; } else { // network is configured without SSID - match other settings @@ -2152,15 +2221,15 @@ bool WiFiScanResult::matches(const WiFiAP &config) const { #ifdef USE_WIFI_WPA2_EAP // BSSID requires auth but no PSK or EAP credentials given - if (this->with_auth_ && (config.get_password().empty() && !config.get_eap().has_value())) + if (this->with_auth_ && (config.password_.empty() && !config.get_eap().has_value())) return false; // BSSID does not require auth, but PSK or EAP credentials given - if (!this->with_auth_ && (!config.get_password().empty() || config.get_eap().has_value())) + if (!this->with_auth_ && (!config.password_.empty() || config.get_eap().has_value())) return false; #else // If PSK given, only match for networks with auth (and vice versa) - if (config.get_password().empty() == this->with_auth_) + if (config.password_.empty() == this->with_auth_) return false; #endif @@ -2173,7 +2242,6 @@ bool WiFiScanResult::matches(const WiFiAP &config) const { bool WiFiScanResult::get_matches() const { return this->matches_; } void WiFiScanResult::set_matches(bool matches) { this->matches_ = matches; } const bssid_t &WiFiScanResult::get_bssid() const { return this->bssid_; } -const std::string &WiFiScanResult::get_ssid() const { return this->ssid_; } uint8_t WiFiScanResult::get_channel() const { return this->channel_; } int8_t WiFiScanResult::get_rssi() const { return this->rssi_; } bool WiFiScanResult::get_with_auth() const { return this->with_auth_; } @@ -2284,7 +2352,7 @@ void WiFiComponent::process_roaming_scan_() { for (const auto &result : this->scan_result_) { // Must be same SSID, different BSSID - if (current_ssid != result.get_ssid() || result.get_bssid() == current_bssid) + if (result.ssid_ != current_ssid || result.get_bssid() == current_bssid) continue; #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 58f19c184a..ac28a1bc81 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -172,12 +172,67 @@ template using wifi_scan_vector_t = std::vector; template using wifi_scan_vector_t = FixedVector; #endif +/// 20-byte string: 18 chars inline + null, heap for longer. Always null-terminated. +/// Used internally for WiFi SSID/password storage to reduce heap fragmentation. +class CompactString { + public: + static constexpr uint8_t MAX_LENGTH = 127; + static constexpr uint8_t INLINE_CAPACITY = 18; // 18 chars + null terminator fits in 19 bytes + + CompactString() : length_(0), is_heap_(0) { this->storage_[0] = '\0'; } + CompactString(const char *str, size_t len); + CompactString(const CompactString &other); + CompactString(CompactString &&other) noexcept; + CompactString &operator=(const CompactString &other); + CompactString &operator=(CompactString &&other) noexcept; + ~CompactString(); + + const char *data() const { return this->is_heap_ ? this->get_heap_ptr_() : this->storage_; } + const char *c_str() const { return this->data(); } // Always null-terminated + size_t size() const { return this->length_; } + bool empty() const { return this->length_ == 0; } + + /// Return a StringRef view of this string (zero-copy) + StringRef ref() const { return StringRef(this->data(), this->size()); } + + bool operator==(const CompactString &other) const; + bool operator!=(const CompactString &other) const { return !(*this == other); } + bool operator==(const StringRef &other) const; + bool operator!=(const StringRef &other) const { return !(*this == other); } + bool operator==(const char *other) const { return *this == StringRef(other); } + bool operator!=(const char *other) const { return !(*this == other); } + + protected: + char *get_heap_ptr_() const { + char *ptr; + std::memcpy(&ptr, this->storage_, sizeof(ptr)); + return ptr; + } + void set_heap_ptr_(char *ptr) { std::memcpy(this->storage_, &ptr, sizeof(ptr)); } + + // Storage for string data. When is_heap_=0, contains the string directly (null-terminated). + // When is_heap_=1, first sizeof(char*) bytes contain pointer to heap allocation. + char storage_[INLINE_CAPACITY + 1]; // 19 bytes: 18 chars + null terminator + uint8_t length_ : 7; // String length (0-127) + uint8_t is_heap_ : 1; // 1 if using heap pointer, 0 if using inline storage + // Total size: 20 bytes (19 bytes storage + 1 byte bitfields) +}; + +static_assert(sizeof(CompactString) == 20, "CompactString must be exactly 20 bytes"); + class WiFiAP { + friend class WiFiComponent; + friend class WiFiScanResult; + public: void set_ssid(const std::string &ssid); + void set_ssid(const char *ssid); + void set_ssid(StringRef ssid) { this->ssid_ = CompactString(ssid.c_str(), ssid.size()); } void set_bssid(const bssid_t &bssid); void clear_bssid(); void set_password(const std::string &password); + void set_password(const char *password); + void set_password(StringRef password) { this->password_ = CompactString(password.c_str(), password.size()); } #ifdef USE_WIFI_WPA2_EAP void set_eap(optional eap_auth); #endif // USE_WIFI_WPA2_EAP @@ -188,10 +243,10 @@ class WiFiAP { void set_manual_ip(optional manual_ip); #endif void set_hidden(bool hidden); - const std::string &get_ssid() const; + StringRef get_ssid() const { return this->ssid_.ref(); } + StringRef get_password() const { return this->password_.ref(); } const bssid_t &get_bssid() const; bool has_bssid() const; - const std::string &get_password() const; #ifdef USE_WIFI_WPA2_EAP const optional &get_eap() const; #endif // USE_WIFI_WPA2_EAP @@ -204,8 +259,8 @@ class WiFiAP { bool get_hidden() const; protected: - std::string ssid_; - std::string password_; + CompactString ssid_; + CompactString password_; #ifdef USE_WIFI_WPA2_EAP optional eap_; #endif // USE_WIFI_WPA2_EAP @@ -220,15 +275,18 @@ class WiFiAP { }; class WiFiScanResult { + friend class WiFiComponent; + public: - WiFiScanResult(const bssid_t &bssid, std::string ssid, uint8_t channel, int8_t rssi, bool with_auth, bool is_hidden); + WiFiScanResult(const bssid_t &bssid, const char *ssid, size_t ssid_len, uint8_t channel, int8_t rssi, bool with_auth, + bool is_hidden); bool matches(const WiFiAP &config) const; bool get_matches() const; void set_matches(bool matches); const bssid_t &get_bssid() const; - const std::string &get_ssid() const; + StringRef get_ssid() const { return this->ssid_.ref(); } uint8_t get_channel() const; int8_t get_rssi() const; bool get_with_auth() const; @@ -242,7 +300,7 @@ class WiFiScanResult { bssid_t bssid_; uint8_t channel_; int8_t rssi_; - std::string ssid_; + CompactString ssid_; int8_t priority_{0}; bool matches_{false}; bool with_auth_; @@ -381,6 +439,8 @@ class WiFiComponent : public Component { void set_passive_scan(bool passive); void save_wifi_sta(const std::string &ssid, const std::string &password); + void save_wifi_sta(const char *ssid, const char *password); + void save_wifi_sta(StringRef ssid, StringRef password) { this->save_wifi_sta(ssid.c_str(), password.c_str()); } // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -545,7 +605,7 @@ class WiFiComponent : public Component { int8_t find_first_non_hidden_index_() const; /// Check if an SSID was seen in the most recent scan results /// Used to skip hidden mode for SSIDs we know are visible - bool ssid_was_seen_in_scan_(const std::string &ssid) const; + bool ssid_was_seen_in_scan_(const CompactString &ssid) const; /// Check if full scan results are needed (captive portal active, improv, listeners) bool needs_full_scan_results_() const; /// Check if network matches any configured network (for scan result filtering) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 6488de8dae..c87345f0bf 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -247,16 +247,16 @@ 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)) { + if (ap.ssid_.size() > sizeof(conf.ssid)) { ESP_LOGE(TAG, "SSID too long"); return false; } - if (ap.get_password().size() > sizeof(conf.password)) { + if (ap.password_.size() > 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(reinterpret_cast(conf.ssid), ap.ssid_.c_str(), ap.ssid_.size()); + memcpy(reinterpret_cast(conf.password), ap.password_.c_str(), ap.password_.size()); if (ap.has_bssid()) { conf.bssid_set = 1; @@ -266,7 +266,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) - if (ap.get_password().empty()) { + if (ap.password_.empty()) { conf.threshold.authmode = AUTH_OPEN; } else { // Set threshold based on configured minimum auth mode @@ -738,8 +738,8 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) { const char *ssid_cstr = reinterpret_cast(it->ssid); if (needs_full || this->matches_configured_network_(ssid_cstr, it->bssid)) { this->scan_result_.emplace_back( - bssid_t{it->bssid[0], it->bssid[1], it->bssid[2], it->bssid[3], it->bssid[4], it->bssid[5]}, - std::string(ssid_cstr, it->ssid_len), it->channel, it->rssi, it->authmode != AUTH_OPEN, it->is_hidden != 0); + bssid_t{it->bssid[0], it->bssid[1], it->bssid[2], it->bssid[3], it->bssid[4], it->bssid[5]}, ssid_cstr, + it->ssid_len, it->channel, it->rssi, it->authmode != AUTH_OPEN, it->is_hidden != 0); } else { this->log_discarded_scan_result_(ssid_cstr, it->bssid, it->rssi, it->channel); } @@ -832,27 +832,27 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; struct softap_config conf {}; - if (ap.get_ssid().size() > sizeof(conf.ssid)) { + if (ap.ssid_.size() > 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(reinterpret_cast(conf.ssid), ap.ssid_.c_str(), ap.ssid_.size()); + conf.ssid_len = static_cast(ap.ssid_.size()); conf.channel = ap.has_channel() ? ap.get_channel() : 1; conf.ssid_hidden = ap.get_hidden(); conf.max_connection = 5; conf.beacon_interval = 100; - if (ap.get_password().empty()) { + if (ap.password_.empty()) { conf.authmode = AUTH_OPEN; *conf.password = 0; } else { conf.authmode = AUTH_WPA2_PSK; - if (ap.get_password().size() > sizeof(conf.password)) { + if (ap.password_.size() > 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(reinterpret_cast(conf.password), ap.password_.c_str(), ap.password_.size()); } ETS_UART_INTR_DISABLE(); diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index d74d083954..52ee482121 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -300,19 +300,19 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - if (ap.get_ssid().size() > sizeof(conf.sta.ssid)) { + if (ap.ssid_.size() > sizeof(conf.sta.ssid)) { ESP_LOGE(TAG, "SSID too long"); return false; } - if (ap.get_password().size() > sizeof(conf.sta.password)) { + if (ap.password_.size() > sizeof(conf.sta.password)) { ESP_LOGE(TAG, "Password too long"); return false; } - memcpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); - memcpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), ap.get_password().size()); + memcpy(reinterpret_cast(conf.sta.ssid), ap.ssid_.c_str(), ap.ssid_.size()); + memcpy(reinterpret_cast(conf.sta.password), ap.password_.c_str(), ap.password_.size()); // The weakest authmode to accept in the fast scan mode - if (ap.get_password().empty()) { + if (ap.password_.empty()) { conf.sta.threshold.authmode = WIFI_AUTH_OPEN; } else { // Set threshold based on configured minimum auth mode @@ -864,8 +864,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { if (needs_full || this->matches_configured_network_(ssid_cstr, record.bssid)) { bssid_t bssid; std::copy(record.bssid, record.bssid + 6, bssid.begin()); - std::string ssid(ssid_cstr); - this->scan_result_.emplace_back(bssid, std::move(ssid), record.primary, record.rssi, + this->scan_result_.emplace_back(bssid, ssid_cstr, strlen(ssid_cstr), record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN, ssid_cstr[0] == '\0'); } else { this->log_discarded_scan_result_(ssid_cstr, record.bssid, record.rssi, record.primary); @@ -1055,26 +1054,26 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - if (ap.get_ssid().size() > sizeof(conf.ap.ssid)) { + if (ap.ssid_.size() > sizeof(conf.ap.ssid)) { ESP_LOGE(TAG, "AP SSID too long"); return false; } - memcpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); + memcpy(reinterpret_cast(conf.ap.ssid), ap.ssid_.c_str(), ap.ssid_.size()); conf.ap.channel = ap.has_channel() ? ap.get_channel() : 1; - conf.ap.ssid_hidden = ap.get_ssid().size(); + conf.ap.ssid_hidden = ap.get_hidden(); conf.ap.max_connection = 5; conf.ap.beacon_interval = 100; - if (ap.get_password().empty()) { + if (ap.password_.empty()) { conf.ap.authmode = WIFI_AUTH_OPEN; *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - if (ap.get_password().size() > sizeof(conf.ap.password)) { + if (ap.password_.size() > sizeof(conf.ap.password)) { ESP_LOGE(TAG, "AP password too long"); return false; } - memcpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), ap.get_password().size()); + memcpy(reinterpret_cast(conf.ap.password), ap.password_.c_str(), ap.password_.size()); } // pairwise cipher of SoftAP, group cipher will be derived using this. diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 5fd9d7663b..2cc05928af 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -193,7 +193,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return false; String ssid = WiFi.SSID(); - if (ssid && strcmp(ssid.c_str(), ap.get_ssid().c_str()) != 0) { + if (ssid && strcmp(ssid.c_str(), ap.ssid_.c_str()) != 0) { WiFi.disconnect(); } @@ -213,7 +213,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { s_sta_state = LTWiFiSTAState::CONNECTING; s_ignored_disconnect_count = 0; - WiFiStatus status = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), + WiFiStatus status = WiFi.begin(ap.ssid_.c_str(), ap.password_.empty() ? NULL : ap.password_.c_str(), ap.get_channel(), // 0 = auto ap.has_bssid() ? ap.get_bssid().data() : NULL); if (status != WL_CONNECTED) { @@ -688,7 +688,7 @@ void WiFiComponent::wifi_scan_done_callback_() { auto &ap = scan->ap[i]; this->scan_result_.emplace_back(bssid_t{ap.bssid.addr[0], ap.bssid.addr[1], ap.bssid.addr[2], ap.bssid.addr[3], ap.bssid.addr[4], ap.bssid.addr[5]}, - std::string(ssid_cstr), ap.channel, ap.rssi, ap.auth != WIFI_AUTH_OPEN, + ssid_cstr, strlen(ssid_cstr), ap.channel, ap.rssi, ap.auth != WIFI_AUTH_OPEN, ssid_cstr[0] == '\0'); } else { auto &ap = scan->ap[i]; @@ -735,7 +735,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { yield(); - return WiFi.softAP(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), + return WiFi.softAP(ap.ssid_.c_str(), ap.password_.empty() ? NULL : ap.password_.c_str(), ap.has_channel() ? ap.get_channel() : 1, ap.get_hidden()); } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 818ad1059c..1baf21e2b2 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -78,7 +78,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return false; #endif - auto ret = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().c_str()); + auto ret = WiFi.begin(ap.ssid_.c_str(), ap.password_.c_str()); if (ret != WL_CONNECTED) return false; @@ -149,9 +149,8 @@ void WiFiComponent::wifi_scan_result(void *env, const cyw43_ev_scan_result_t *re bssid_t bssid; std::copy(result->bssid, result->bssid + 6, bssid.begin()); - std::string ssid(ssid_cstr); - WiFiScanResult res(bssid, std::move(ssid), result->channel, result->rssi, result->auth_mode != CYW43_AUTH_OPEN, - ssid_cstr[0] == '\0'); + WiFiScanResult res(bssid, ssid_cstr, strlen(ssid_cstr), result->channel, result->rssi, + result->auth_mode != CYW43_AUTH_OPEN, ssid_cstr[0] == '\0'); if (std::find(this->scan_result_.begin(), this->scan_result_.end(), res) == this->scan_result_.end()) { this->scan_result_.push_back(res); } @@ -204,7 +203,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { } #endif - WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.has_channel() ? ap.get_channel() : 1); + WiFi.beginAP(ap.ssid_.c_str(), ap.password_.c_str(), ap.has_channel() ? ap.get_channel() : 1); return true; } diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index a63b30b892..b5ebfd7390 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -89,7 +89,7 @@ void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t end) break;