Merge branch 'compact_string_wifi' into integration

This commit is contained in:
J. Nick Koston
2026-02-11 14:53:45 -06:00
10 changed files with 179 additions and 174 deletions
@@ -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);
@@ -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);
+88 -24
View File
@@ -20,6 +20,7 @@
#endif
#include <algorithm>
#include <new>
#include <utility>
#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
@@ -353,14 +417,14 @@ 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<int8_t>(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<int>(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<int>(i));
return static_cast<int8_t>(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();
@@ -999,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 {
@@ -1039,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
@@ -1414,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;
@@ -1830,9 +1894,9 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() {
// Get SSID for logging (use pointer to avoid copy)
const char *ssid = nullptr;
if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) {
ssid = this->scan_result_[0].get_ssid().c_str();
ssid = this->scan_result_[0].ssid_.c_str();
} else if (const WiFiAP *config = this->get_selected_sta_()) {
ssid = config->get_ssid().c_str();
ssid = config->ssid_.c_str();
}
// Only decrease priority on the last attempt for this phase
@@ -2145,9 +2209,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
@@ -2158,15 +2222,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
@@ -2289,7 +2353,7 @@ void WiFiComponent::process_roaming_scan_() {
for (const auto &result : this->scan_result_) {
// Must be same SSID, different BSSID
if (result.get_ssid() != current_ssid.c_str() || result.get_bssid() == current_bssid)
if (result.ssid_ != current_ssid || result.get_bssid() == current_bssid)
continue;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
+60 -8
View File
@@ -172,16 +172,68 @@ template<typename T> using wifi_scan_vector_t = std::vector<T>;
template<typename T> using wifi_scan_vector_t = FixedVector<T>;
#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
static constexpr uint8_t BUFFER_SIZE = MAX_LENGTH + 1; // For external buffer (128 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(const CompactString &ssid) { this->ssid_ = 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(const CompactString &password) { this->password_ = password; }
void set_password(StringRef password) { this->password_ = CompactString(password.c_str(), password.size()); }
#ifdef USE_WIFI_WPA2_EAP
void set_eap(optional<EAPAuth> eap_auth);
#endif // USE_WIFI_WPA2_EAP
@@ -192,8 +244,8 @@ class WiFiAP {
void set_manual_ip(optional<ManualIP> manual_ip);
#endif
void set_hidden(bool hidden);
const CompactString &get_ssid() const { return this->ssid_; }
const CompactString &get_password() const { return this->password_; }
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;
#ifdef USE_WIFI_WPA2_EAP
@@ -224,6 +276,8 @@ class WiFiAP {
};
class WiFiScanResult {
friend class WiFiComponent;
public:
WiFiScanResult(const bssid_t &bssid, const char *ssid, size_t ssid_len, uint8_t channel, int8_t rssi, bool with_auth,
bool is_hidden);
@@ -233,7 +287,7 @@ class WiFiScanResult {
bool get_matches() const;
void set_matches(bool matches);
const bssid_t &get_bssid() const;
const CompactString &get_ssid() const { return this->ssid_; }
StringRef get_ssid() const { return this->ssid_.ref(); }
uint8_t get_channel() const;
int8_t get_rssi() const;
bool get_with_auth() const;
@@ -387,9 +441,7 @@ class WiFiComponent : public Component {
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(const CompactString &ssid, const CompactString &password) {
this->save_wifi_sta(ssid.c_str(), password.c_str());
}
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)
@@ -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<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
memcpy(reinterpret_cast<char *>(conf.ssid), ap.ssid_.c_str(), ap.ssid_.size());
memcpy(reinterpret_cast<char *>(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
@@ -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<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
conf.ssid_len = static_cast<uint8>(ap.get_ssid().size());
memcpy(reinterpret_cast<char *>(conf.ssid), ap.ssid_.c_str(), ap.ssid_.size());
conf.ssid_len = static_cast<uint8>(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<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
memcpy(reinterpret_cast<char *>(conf.password), ap.password_.c_str(), ap.password_.size());
}
ETS_UART_INTR_DISABLE();
@@ -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<char *>(conf.sta.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
memcpy(reinterpret_cast<char *>(conf.sta.password), ap.get_password().c_str(), ap.get_password().size());
memcpy(reinterpret_cast<char *>(conf.sta.ssid), ap.ssid_.c_str(), ap.ssid_.size());
memcpy(reinterpret_cast<char *>(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
@@ -1054,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<char *>(conf.ap.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
memcpy(reinterpret_cast<char *>(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<char *>(conf.ap.password), ap.get_password().c_str(), ap.get_password().size());
memcpy(reinterpret_cast<char *>(conf.ap.password), ap.password_.c_str(), ap.password_.size());
}
// pairwise cipher of SoftAP, group cipher will be derived using this.
@@ -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) {
@@ -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());
}
@@ -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;
@@ -203,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;
}
-57
View File
@@ -13,7 +13,6 @@
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <new>
#ifdef USE_ESP32
#include "rom/crc.h"
@@ -860,60 +859,4 @@ void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us) {
;
}
// 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)
}
}
} // namespace esphome
-54
View File
@@ -1866,58 +1866,4 @@ template<typename T, enable_if_t<std::is_pointer<T *>::value, int> = 0> T &id(T
///@}
/// 20-byte string: 18 chars inline + null, heap for longer. Always null-terminated.
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
static constexpr uint8_t BUFFER_SIZE = MAX_LENGTH + 1; // For external buffer (128 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; }
// Implicit conversion to std::string for backwards compatibility
operator std::string() const { return std::string(this->data(), this->size()); }
bool operator==(const CompactString &other) const {
return this->size() == other.size() && std::memcmp(this->data(), other.data(), this->size()) == 0;
}
bool operator==(const std::string &other) const {
return this->size() == other.size() && std::memcmp(this->data(), other.data(), this->size()) == 0;
}
bool operator==(const char *other) const {
return this->size() == std::strlen(other) && std::memcmp(this->data(), other, this->size()) == 0;
}
bool operator!=(const CompactString &other) const { return !(*this == other); }
bool operator!=(const std::string &other) const { return !(*this == 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");
} // namespace esphome