9#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
10#include <esp_eap_client.h>
20#include <user_interface.h>
36#ifdef USE_CAPTIVE_PORTAL
44#ifdef USE_IMPROV_SERIAL
48#ifdef USE_PROVISIONING
54static const char *
const TAG =
"wifi";
73 char *heap_data =
new char[
len + 1];
74 std::memcpy(heap_data, str,
len);
75 heap_data[
len] =
'\0';
92 std::memcpy(this->storage_, other.storage_, INLINE_CAPACITY + 1);
95 other.storage_[0] =
'\0';
113 return this->
size() == other.
size() && std::memcmp(this->
data(), other.
data(), this->size()) == 0;
116 return this->
size() == other.
size() && std::memcmp(this->
data(), other.
c_str(), this->size()) == 0;
315#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_INFO
316#ifdef USE_WIFI_PHY_MODE
320 return LOG_STR(
"11B");
322 return LOG_STR(
"11G");
324 return LOG_STR(
"11N");
325 return LOG_STR(
"Auto");
329static const LogString *retry_phase_to_log_string(
WiFiRetryPhase phase) {
331 return LOG_STR(
"INITIAL_CONNECT");
332#ifdef USE_WIFI_FAST_CONNECT
334 return LOG_STR(
"FAST_CONNECT_CYCLING");
337 return LOG_STR(
"EXPLICIT_HIDDEN");
339 return LOG_STR(
"SCAN_CONNECTING");
341 return LOG_STR(
"RETRY_HIDDEN");
343 return LOG_STR(
"RESTARTING");
344 return LOG_STR(
"UNKNOWN");
351 return !this->
sta_.empty() && this->
sta_[0].get_hidden();
357 for (
size_t i = 0; i < this->
sta_.size(); i++) {
358 if (!this->
sta_[i].get_hidden()) {
359 return static_cast<int8_t
>(i);
370static constexpr uint8_t WIFI_RETRY_COUNT_PER_BSSID = 2;
374static constexpr uint8_t WIFI_RETRY_COUNT_PER_SSID = 1;
378static constexpr uint8_t WIFI_RETRY_COUNT_PER_AP = 1;
382static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 500;
385#ifdef USE_WIFI_AP_EXCLUSIVE
386static constexpr bool WIFI_AP_EXCLUSIVE =
true;
388static constexpr bool WIFI_AP_EXCLUSIVE =
false;
391#ifdef USE_WIFI_AP_EXCLUSIVE
396static constexpr uint32_t WIFI_AP_EXCLUSIVE_DWELL_MS = 300000;
399static constexpr uint32_t WIFI_AP_EXCLUSIVE_MAX_DWELL_MS = 3 * WIFI_AP_EXCLUSIVE_DWELL_MS;
400static constexpr uint32_t WIFI_AP_EXCLUSIVE_HANDOVER_MS = 1000;
401static constexpr uint32_t AP_HANDOVER_TIMEOUT_ID = 0;
407static constexpr uint32_t WIFI_COOLDOWN_WITH_AP_ACTIVE_MS = 30000;
412static constexpr uint32_t WIFI_SCAN_TIMEOUT_MS = 31000;
422static constexpr uint32_t WIFI_CONNECT_TIMEOUT_MS = 46000;
424static constexpr uint8_t get_max_retries_for_phase(
WiFiRetryPhase phase) {
427#ifdef USE_WIFI_FAST_CONNECT
431 return WIFI_RETRY_COUNT_PER_AP;
434 return WIFI_RETRY_COUNT_PER_SSID;
437 return WIFI_RETRY_COUNT_PER_BSSID;
440 return WIFI_RETRY_COUNT_PER_SSID;
442 return WIFI_RETRY_COUNT_PER_BSSID;
446static void apply_scan_result_to_params(WiFiAP ¶ms,
const WiFiScanResult &scan) {
447 params.set_hidden(
false);
448 params.set_ssid(scan.get_ssid());
449 params.set_bssid(scan.get_bssid());
450 params.set_channel(scan.get_channel());
465 for (
const auto &conf : this->
sta_) {
466 if (conf.ssid_ == ssid && conf.get_hidden()) {
473 if (scan.ssid_ == ssid) {
487#ifdef USE_CAPTIVE_PORTAL
494#ifdef USE_IMPROV_SERIAL
513 if (ssid[0] ==
'\0') {
516 for (
const auto &sta : this->
sta_) {
518 if (sta.get_hidden()) {
522 if (sta.ssid_.empty()) {
523 if (sta.has_bssid() && std::memcmp(sta.get_bssid().data(), bssid, 6) == 0) {
529 if (sta.ssid_ == ssid) {
538 if (it.bssid == bssid) {
550#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
556 char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
558 ESP_LOGV(TAG,
"- " LOG_SECRET(
"'%s'")
" " LOG_SECRET(
"(%s)")
" %ddB Ch:%u", ssid, bssid_s, rssi, channel);
582 for (
size_t i = start_index + 1; i < this->
sta_.size(); i++) {
583 const auto &sta = this->
sta_[i];
588 if (!include_explicit_hidden && sta.get_hidden()) {
590 if (first_non_hidden_idx < 0 ||
static_cast<int8_t
>(i) < first_non_hidden_idx) {
591 ESP_LOGD(TAG,
"Skipping " LOG_SECRET(
"'%s'")
" (explicit hidden, already tried)", sta.ssid_.c_str());
599 ESP_LOGD(TAG,
"Hidden candidate " LOG_SECRET(
"'%s'")
" at index %d", sta.ssid_.c_str(),
static_cast<int>(i));
600 return static_cast<int8_t
>(i);
602 ESP_LOGD(TAG,
"Skipping hidden retry for visible network " LOG_SECRET(
"'%s'"), sta.ssid_.c_str());
611 if (!this->
sta_.empty() && this->sta_[0].get_hidden()) {
612 ESP_LOGI(TAG,
"Starting with explicit hidden network (highest priority)");
622#if defined(USE_ESP32) && defined(USE_WIFI_WPA2_EAP) && ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
623static const char *eap_phase2_to_str(esp_eap_ttls_phase2_types
type) {
625 case ESP_EAP_TTLS_PHASE2_PAP:
627 case ESP_EAP_TTLS_PHASE2_CHAP:
629 case ESP_EAP_TTLS_PHASE2_MSCHAP:
631 case ESP_EAP_TTLS_PHASE2_MSCHAPV2:
633 case ESP_EAP_TTLS_PHASE2_EAP:
644#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
649 ESP_LOGE(TAG,
"Failed semaphore");
656#if defined(USE_PROVISIONING) && defined(USE_WIFI_AP)
664 ESP_LOGD(TAG,
"Provisioning window closed; disabling AP");
665#ifdef USE_WIFI_AP_EXCLUSIVE
690 ESP_LOGCONFIG(TAG,
"Starting");
696#ifdef USE_WIFI_FAST_CONNECT
697#ifdef USE_WIFI_FAST_CONNECT_IN_FLASH
698 const bool fast_connect_in_flash =
true;
700 const bool fast_connect_in_flash =
false;
708 ESP_LOGD(TAG,
"Loaded settings: %s", save.ssid);
712 sta.set_password(save.password);
719 ESP_LOGV(TAG,
"Setting Output Power Option failed");
722#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
726 if (semaphore_count > 0) {
736 ESP_LOGV(TAG,
"Setting Power Save Option failed");
740#ifdef USE_WIFI_FAST_CONNECT
745 if (loaded_fast_connect) {
746 ESP_LOGI(TAG,
"Starting fast_connect (saved) " LOG_SECRET(
"'%s'"), params.
ssid_.
c_str());
748 }
else if (!this->
sta_.empty() && !this->sta_[0].get_hidden()) {
750 ESP_LOGI(TAG,
"Starting fast_connect (config) " LOG_SECRET(
"'%s'"), this->
sta_[0].ssid_.c_str());
763 }
else if (this->
has_ap()) {
766 ESP_LOGV(TAG,
"Setting Output Power Option failed");
768#ifdef USE_CAPTIVE_PORTAL
771 if (!WIFI_AP_EXCLUSIVE) {
790 ESP_LOGW(TAG,
"Restarting adapter");
813#if defined(USE_WIFI_CONNECT_TRIGGER) || defined(USE_WIFI_DISCONNECT_TRIGGER)
815#ifdef USE_WIFI_DISCONNECT_TRIGGER
820#ifdef USE_WIFI_CONNECT_TRIGGER
840 uint32_t cooldown_duration = portal_active ? WIFI_COOLDOWN_WITH_AP_ACTIVE_MS : WIFI_COOLDOWN_DURATION_MS;
864 ESP_LOGW(TAG,
"Connection lost; reconnecting");
871#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
897#ifdef USE_WIFI_AP_EXCLUSIVE
913 bool provisioning_closed =
false;
914#ifdef USE_PROVISIONING
918 provisioning_closed =
924#ifdef USE_WIFI_AP_EXCLUSIVE
930 ESP_LOGI(TAG,
"Starting fallback AP");
931#ifdef USE_WIFI_AP_EXCLUSIVE
935#ifdef USE_CAPTIVE_PORTAL
962 bool suppress =
false;
963#ifdef USE_PROVISIONING
973 ESP_LOGE(TAG,
"Can't connect; rebooting");
980#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
988 ESP_LOGV(TAG,
"Switching to high-performance mode (%" PRIu32
" active %s)", (
uint32_t) semaphore_count,
989 semaphore_count == 1 ? LOG_STR_LITERAL(
"request") : LOG_STR_LITERAL(
"requests"));
996 ESP_LOGV(TAG,
"Restoring power save mode to configured setting");
1027#ifdef USE_WIFI_AP_EXCLUSIVE
1041 static constexpr size_t AP_SSID_MAX_LEN = 32;
1042 static constexpr size_t AP_SSID_PREFIX_LEN = 25;
1043 static constexpr size_t AP_SSID_SUFFIX_LEN = 7;
1046 const char *name_ptr = app_name.
c_str();
1047 size_t name_len = app_name.length();
1049 if (name_len <= AP_SSID_MAX_LEN) {
1054 char ssid_buf[AP_SSID_MAX_LEN + 1];
1057 memcpy(ssid_buf, name_ptr, AP_SSID_PREFIX_LEN);
1058 memcpy(ssid_buf + AP_SSID_PREFIX_LEN, name_ptr + name_len - AP_SSID_SUFFIX_LEN, AP_SSID_SUFFIX_LEN);
1060 memcpy(ssid_buf, name_ptr, AP_SSID_MAX_LEN);
1062 ssid_buf[AP_SSID_MAX_LEN] =
'\0';
1067#ifdef USE_WIFI_AP_EXCLUSIVE
1074 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
1078 " AP Password: '%s'\n"
1080 this->
ap_.
ssid_.
c_str(), this->ap_.password_.c_str(), this->wifi_soft_ap_ip().str_to(ip_buf));
1082#ifdef USE_WIFI_MANUAL_IP
1084 if (manual_ip.has_value()) {
1085 char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
1086 char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
1087 char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
1089 " AP Static IP: '%s'\n"
1090 " AP Gateway: '%s'\n"
1092 manual_ip->static_ip.str_to(static_ip_buf), manual_ip->gateway.str_to(gateway_buf),
1093 manual_ip->subnet.str_to(subnet_buf));
1104#ifdef USE_WIFI_AP_EXCLUSIVE
1106 ESP_LOGI(TAG,
"Pausing AP to try the networks");
1122#ifdef USE_CAPTIVE_PORTAL
1127 ESP_LOGD(TAG,
"Disabling AP");
1156 if (config ==
nullptr) {
1157 ESP_LOGE(TAG,
"No valid network config (selected_sta_index_=%d, sta_.size()=%zu)",
1167#ifdef USE_WIFI_FAST_CONNECT
1185 apply_scan_result_to_params(params, this->
scan_result_[0]);
1199 return config ? *config :
WiFiAP{};
1206 strncpy(save.ssid, ssid,
sizeof(save.ssid) - 1);
1207 strncpy(save.password, password,
sizeof(save.password) - 1);
1214 sta.set_password(password);
1222#ifdef USE_WIFI_AP_EXCLUSIVE
1226 this->
set_timeout(AP_HANDOVER_TIMEOUT_ID, WIFI_AP_EXCLUSIVE_HANDOVER_MS, [
this]() {
1235 ESP_LOGD(TAG,
"Exiting cooldown early due to new WiFi credentials");
1242 char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
1251 "Connecting to " LOG_SECRET(
"'%s'")
" " LOG_SECRET(
"(%s)")
" (priority %d, attempt %u/%u in phase %s)...",
1255#ifdef ESPHOME_LOG_HAS_VERBOSE
1257 "Connection Params:\n"
1261 ESP_LOGV(TAG,
" BSSID: %s", bssid_s);
1263 ESP_LOGV(TAG,
" BSSID: Not Set");
1266#ifdef USE_WIFI_WPA2_EAP
1267 const auto &eap_opt = ap.
get_eap();
1268 if (eap_opt.has_value()) {
1269 const EAPAuth &eap_config = *eap_opt;
1273 " WPA2 Enterprise authentication configured:\n"
1274 " Identity: " LOG_SECRET(
"'%s'")
"\n"
1275 " Username: " LOG_SECRET(
"'%s'")
"\n"
1276 " Password: " LOG_SECRET(
"'%s'"),
1279#if defined(USE_ESP32) && defined(USE_WIFI_WPA2_EAP) && ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
1280 ESP_LOGV(TAG,
" TTLS Phase 2: " LOG_SECRET(
"'%s'"), eap_phase2_to_str(eap_config.
ttls_phase_2));
1282 bool ca_cert_present = eap_config.
ca_cert !=
nullptr && strlen(eap_config.
ca_cert);
1287 " Client Cert: %s\n"
1289 ca_cert_present ? LOG_STR_LITERAL(
"present") : LOG_STR_LITERAL(
"not present"),
1290 client_cert_present ? LOG_STR_LITERAL(
"present") : LOG_STR_LITERAL(
"not present"),
1291 client_key_present ? LOG_STR_LITERAL(
"present") : LOG_STR_LITERAL(
"not present"));
1294 ESP_LOGV(TAG,
" Password: " LOG_SECRET(
"'%s'"), ap.
password_.
c_str());
1295#ifdef USE_WIFI_WPA2_EAP
1301 ESP_LOGV(TAG,
" Channel not set");
1303#ifdef USE_WIFI_MANUAL_IP
1305 if (manual_ip.has_value()) {
1307 char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
1308 char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
1309 char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
1310 char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
1311 char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
1312 ESP_LOGV(TAG,
" Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s",
m.static_ip.str_to(static_ip_buf),
1313 m.gateway.str_to(gateway_buf),
m.subnet.str_to(subnet_buf),
m.dns1.str_to(dns1_buf),
1314 m.dns2.str_to(dns2_buf));
1318 ESP_LOGV(TAG,
" Using DHCP IP");
1320 ESP_LOGV(TAG,
" Hidden: %s", YESNO(ap.
get_hidden()));
1331 ESP_LOGE(TAG,
"wifi_sta_connect_ failed");
1351 return LOG_STR(
"\033[0;32m"
1357 }
else if (rssi >= -65) {
1358 return LOG_STR(
"\033[0;33m"
1365 }
else if (rssi >= -85) {
1366 return LOG_STR(
"\033[0;33m"
1374 return LOG_STR(
"\033[0;31m"
1386 char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
1389 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
1392 ESP_LOGCONFIG(TAG,
" IP Address: %s", ip.str_to(ip_buf));
1397 char ssid_buf[SSID_BUFFER_SIZE];
1398 char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
1399 char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
1400 char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
1401 char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
1404 " SSID: " LOG_SECRET(
"'%s'")
"\n"
1405 " BSSID: " LOG_SECRET(
"%s")
"\n"
1407 " Signal strength: %d dB %s\n"
1408 " Channel: %" PRId32
"\n"
1417#ifdef ESPHOME_LOG_HAS_VERBOSE
1419 ESP_LOGV(TAG,
" Priority: %d", this->
get_sta_priority(config->get_bssid()));
1422#ifdef USE_WIFI_11KV_SUPPORT
1426 this->
btm_ ? LOG_STR_LITERAL(
"enabled") : LOG_STR_LITERAL(
"disabled"),
1427 this->
rrm_ ? LOG_STR_LITERAL(
"enabled") : LOG_STR_LITERAL(
"disabled"));
1435 ESP_LOGD(TAG,
"Enabling");
1449 ESP_LOGD(TAG,
"Disabling");
1457 ESP_LOGD(TAG,
"Starting scan");
1509 return a.
get_rssi() > b.get_rssi();
1535template<
typename VectorType>
static void insertion_sort_scan_results(VectorType &results) {
1541 static_assert(!std::is_polymorphic<WiFiScanResult>::value,
"WiFiScanResult must not have vtable");
1542 static_assert(!std::is_polymorphic<CompactString>::value,
"CompactString must not have vtable");
1545 static_assert(std::is_standard_layout<WiFiScanResult>::value,
"WiFiScanResult must be standard layout");
1546 static_assert(std::is_standard_layout<CompactString>::value,
"CompactString must be standard layout");
1548 static_assert(
sizeof(WiFiScanResult) == 32,
"WiFiScanResult size changed - verify memcpy sort is still safe");
1549 static_assert(
sizeof(CompactString) == 20,
"CompactString size changed - verify memcpy sort is still safe");
1551 static_assert(
alignof(WiFiScanResult) <=
alignof(std::max_align_t),
"WiFiScanResult alignment exceeds max_align_t");
1552 const size_t size = results.size();
1553 constexpr size_t elem_size =
sizeof(WiFiScanResult);
1557 auto *memcpy_fn = &memcpy;
1558 for (
size_t i = 1; i <
size; i++) {
1559 alignas(WiFiScanResult) uint8_t key_buf[
elem_size];
1560 memcpy_fn(key_buf, &results[i],
elem_size);
1561 const auto &key = *
reinterpret_cast<const WiFiScanResult *
>(key_buf);
1566 while (j >= 0 && wifi_scan_result_is_better(key, results[j])) {
1567 memcpy_fn(&results[j + 1], &results[j],
elem_size);
1570 memcpy_fn(&results[j + 1], key_buf,
elem_size);
1582 char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
1586#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
1588 ESP_LOGI(TAG,
"- '%s' %s" LOG_SECRET(
"(%s) ")
"%s Ch:%2u %3ddB P:%d", res.
get_ssid().
c_str(),
1589 res.
get_is_hidden() ? LOG_STR_LITERAL(
"(HIDDEN) ") : LOG_STR_LITERAL(
""), bssid_s,
1592 ESP_LOGI(TAG,
"- '%s' %s" LOG_SECRET(
"(%s) ")
"%s", res.
get_ssid().
c_str(),
1593 res.
get_is_hidden() ? LOG_STR_LITERAL(
"(HIDDEN) ") : LOG_STR_LITERAL(
""), bssid_s,
1601 ESP_LOGE(TAG,
"Scan timeout");
1612 ESP_LOGW(TAG,
"No networks found");
1617 ESP_LOGD(TAG,
"Found networks:");
1621 for (
auto &ap : this->
sta_) {
1622 if (res.matches(ap)) {
1623 res.set_matches(
true);
1625 const bssid_t &bssid = res.get_bssid();
1636 insertion_sort_scan_results(this->scan_result_);
1641 if (res.get_matches()) {
1642 log_scan_result(res);
1651 bool found_match =
false;
1653 for (
size_t i = 0; i < this->
sta_.size(); i++) {
1654 if (scan_res.
matches(this->sta_[i])) {
1665 ESP_LOGW(TAG,
"No matching network found");
1687 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
1694 ESP_LOGCONFIG(TAG,
" Disabled");
1697#if defined(USE_ESP32) && defined(SOC_WIFI_SUPPORT_5G)
1698 const char *band_mode_s;
1700 case WIFI_BAND_MODE_2G_ONLY:
1701 band_mode_s =
"2.4GHz";
1703 case WIFI_BAND_MODE_5G_ONLY:
1704 band_mode_s =
"5GHz";
1706 case WIFI_BAND_MODE_AUTO:
1708 band_mode_s =
"Auto";
1711 ESP_LOGCONFIG(TAG,
" Band Mode: %s", band_mode_s);
1713#ifdef USE_WIFI_PHY_MODE
1714 ESP_LOGCONFIG(TAG,
" PHY Mode: %s", LOG_STR_ARG(phy_mode_to_log_string(this->
phy_mode_)));
1725 char ssid_buf[SSID_BUFFER_SIZE];
1727 ESP_LOGW(TAG,
"Connection incomplete");
1732 ESP_LOGI(TAG,
"Connected");
1738 ESP_LOGW(TAG, LOG_SECRET(
"'%s'")
" should be marked hidden", config->ssid_.c_str());
1748#ifdef USE_IMPROV_BLE
1766 ESP_LOGD(TAG,
"Roam successful");
1773 char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
1797#ifdef USE_WIFI_FAST_CONNECT
1803#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
1809#if defined(USE_ESP8266) && defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
1821 ESP_LOGW(TAG,
"Connection timeout, aborting connection attempt");
1831 ESP_LOGW(TAG,
"Connecting to network failed (callback)");
1842 ESP_LOGW(TAG,
"Network no longer found");
1848 ESP_LOGW(TAG,
"Connecting to network failed");
1853 ESP_LOGW(TAG,
"Unknown connection status %d", (
int)
status);
1866#ifdef USE_WIFI_FAST_CONNECT
1875 if (!this->
sta_.empty() && this->sta_[0].get_hidden()) {
1883 if (this->
num_retried_ + 1 < WIFI_RETRY_COUNT_PER_SSID) {
1890 if (next_index < this->
sta_.size() && this->sta_[next_index].get_hidden()) {
1906 if (this->
scan_result_.empty() || !this->scan_result_[0].get_matches()) {
1910 if (this->
num_retried_ + 1 < WIFI_RETRY_COUNT_PER_BSSID) {
1929 if (this->
num_retried_ + 1 < WIFI_RETRY_COUNT_PER_SSID) {
2006 if (old_phase == new_phase) {
2010 ESP_LOGD(TAG,
"Retry phase: %s → %s", LOG_STR_ARG(retry_phase_to_log_string(old_phase)),
2011 LOG_STR_ARG(retry_phase_to_log_string(new_phase)));
2017 switch (new_phase) {
2018#ifdef USE_WIFI_FAST_CONNECT
2035#ifdef USE_WIFI_FAST_CONNECT
2037 ESP_LOGI(TAG,
"Fast connect exhausted, falling back to scan");
2065 ESP_LOGD(TAG,
"All SSIDs visible or already tried, skipping hidden mode");
2118 if (first_priority != std::numeric_limits<int8_t>::min()) {
2123 if (pri.priority != first_priority) {
2129 ESP_LOGD(TAG,
"Clearing BSSID priorities (all at minimum)");
2153 optional<bssid_t> failed_bssid;
2160 failed_bssid = config->get_bssid();
2163 if (!failed_bssid.has_value()) {
2168 const char *ssid =
nullptr;
2172 ssid = config->ssid_.c_str();
2177 uint8_t max_retries = get_max_retries_for_phase(this->
retry_phase_);
2178 bool is_last_attempt = (this->
num_retried_ + 1 >= max_retries);
2182 int8_t new_priority = old_priority;
2184 if (is_last_attempt) {
2187 (old_priority > std::numeric_limits<int8_t>::min()) ? (old_priority - 1) : std::numeric_limits<int8_t>::min();
2190 char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
2192 ESP_LOGD(TAG,
"Failed " LOG_SECRET(
"'%s'")
" " LOG_SECRET(
"(%s)")
", priority %d → %d", ssid !=
nullptr ? ssid :
"",
2193 bssid_s, old_priority, new_priority);
2214#ifdef USE_WIFI_FAST_CONNECT
2219 ESP_LOGD(TAG,
"Next AP in %s", LOG_STR_ARG(retry_phase_to_log_string(this->
retry_phase_)));
2228 if (next_index < this->
sta_.size() && this->sta_[next_index].get_hidden()) {
2231 ESP_LOGD(TAG,
"Next explicit hidden network at index %d",
static_cast<int>(next_index));
2243 if (next_index != -1) {
2263 ESP_LOGD(TAG,
"Retry attempt %u/%u in phase %s", this->
num_retried_ + 1,
2304 if (next_phase == current_phase) {
2320#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
2326#ifdef USE_CAPTIVE_PORTAL
2333#ifdef USE_IMPROV_BLE
2340#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
2380#ifdef USE_WIFI_FAST_CONNECT
2386 if (fast_connect_save.ap_index < 0 ||
static_cast<size_t>(fast_connect_save.ap_index) >= this->sta_.size()) {
2387 ESP_LOGW(TAG,
"AP index out of bounds");
2395 params = this->
sta_[fast_connect_save.ap_index];
2399 std::copy(fast_connect_save.bssid, fast_connect_save.bssid + 6, bssid.begin());
2405 ESP_LOGD(TAG,
"Loaded fast_connect settings");
2406#if defined(USE_ESP32) && defined(SOC_WIFI_SUPPORT_5G)
2408 (this->band_mode_ == WIFI_BAND_MODE_2G_ONLY && fast_connect_save.channel >=
FIRST_5GHZ_CHANNEL)) {
2409 ESP_LOGW(TAG,
"Saved channel %u not allowed by band mode, ignoring fast_connect", fast_connect_save.channel);
2427 if (this->
fast_connect_pref_.
load(&previous_save) && memcmp(previous_save.bssid, bssid.data(), 6) == 0 &&
2428 previous_save.channel == channel && previous_save.ap_index == ap_index) {
2433 memcpy(fast_connect_save.bssid, bssid.data(), 6);
2434 fast_connect_save.channel = channel;
2435 fast_connect_save.ap_index = ap_index;
2439 ESP_LOGD(TAG,
"Saved fast_connect settings");
2444 bool with_auth,
bool is_hidden)
2448 ssid_(ssid, ssid_len),
2449 with_auth_(with_auth),
2450 is_hidden_(is_hidden) {}
2468#ifdef USE_WIFI_WPA2_EAP
2511#ifdef USE_WIFI_FAST_CONNECT
2530#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
2534 this->
pending_.connect_state =
false;
2536 char ssid_buf[SSID_BUFFER_SIZE];
2540 listener->on_wifi_connect_state(
StringRef(ssid, strlen(ssid)), bssid);
2545 constexpr uint8_t empty_bssid[6] = {};
2547 listener->on_wifi_connect_state(
StringRef(), empty_bssid);
2552#ifdef USE_WIFI_IP_STATE_LISTENERS
2560#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
2570 ESP_LOGD(TAG,
"Roam check requested, but not able to check now");
2582 if (selected ==
nullptr || selected->
get_hidden()) {
2593 ESP_LOGD(TAG,
"Roam check skipped, signal good (%d dBm, attempt %u/%u)", rssi, this->
roaming_attempts_,
2602 ESP_LOGD(TAG,
"Roam scan failed to start");
2616 int8_t current_rssi = this->
wifi_rssi();
2618 if (current_rssi == WIFI_RSSI_DISCONNECTED) {
2623 char ssid_buf[SSID_BUFFER_SIZE];
2629 char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
2633 if (result.ssid_ != current_ssid || result.get_bssid() == current_bssid)
2636#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
2638 ESP_LOGV(TAG,
"Roam candidate %s %d dBm", bssid_buf, result.get_rssi());
2649 int8_t improvement = (best ==
nullptr) ? 0 : best->
get_rssi() - current_rssi;
2658 ESP_LOGI(TAG,
"Roaming to %s (%+d dB)", bssid_buf, improvement);
2660 WiFiAP roam_params = *selected;
2661 apply_scan_result_to_params(roam_params, *best);
BedjetMode mode
BedJet operating mode.
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
void wake_loop_threadsafe()
Wake the main event loop from another thread or callback.
bool is_name_add_mac_suffix_enabled() const
uint32_t get_config_version_hash()
Get the config hash extended with ESPHome version.
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void status_set_warning()
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
void status_clear_warning()
StringRef is a reference to a string owned by something else.
constexpr const char * c_str() const
constexpr size_type size() const
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
bool window_pending() const
void add_on_closed_callback(F &&callback)
20-byte string: 18 chars inline + null, heap for longer.
const char * data() const
CompactString & operator=(const CompactString &other)
bool operator==(const CompactString &other) const
static constexpr uint8_t INLINE_CAPACITY
const char * c_str() const
char storage_[INLINE_CAPACITY+1]
static constexpr uint8_t MAX_LENGTH
void set_heap_ptr_(char *ptr)
char * get_heap_ptr_() const
Guards WiFiComponent::scan_result_.
uint8_t get_channel() const
void set_ssid(const std::string &ssid)
const optional< EAPAuth > & get_eap() const
void set_bssid(const bssid_t &bssid)
void set_channel(uint8_t channel)
const optional< ManualIP > & get_manual_ip() const
void set_hidden(bool hidden)
const bssid_t & get_bssid() const
This component is responsible for managing the ESP WiFi interface.
void notify_scan_results_listeners_()
Notify scan results listeners with current scan results.
bool is_captive_portal_active_()
bool error_from_callback_
void add_sta(const WiFiAP &ap)
bool load_fast_connect_settings_(WiFiAP ¶ms)
bool wifi_apply_power_save_()
void set_ap(const WiFiAP &ap)
Setup an Access Point that should be created if no connection to a station can be made.
bool request_high_performance()
Request high-performance mode (no power saving) for improved WiFi latency.
ESPPreferenceObject pref_
void set_sta(const WiFiAP &ap)
bool roaming_suppressed_() const
Returns true if a component has requested that roaming scans be suppressed (e.g. during audio playbac...
uint32_t ap_exclusive_changed_
WiFiPowerSaveMode power_save_
bool has_sta_priority(const bssid_t &bssid)
const WiFiAP * get_selected_sta_() const
WiFiSTAConnectStatus wifi_sta_connect_status_() const
uint32_t roaming_last_check_
WiFi8266PhyMode phy_mode_
int8_t get_sta_priority(const bssid_t bssid)
void log_and_adjust_priority_for_failed_connect_()
Log failed connection and decrease BSSID priority to avoid repeated attempts.
void init_sta(size_t count)
void save_wifi_sta(const std::string &ssid, const std::string &password)
void notify_connect_state_listeners_()
Notify connect state listeners (called after state machine reaches STA_CONNECTED)
WiFiComponentState state_
wifi_scan_vector_t< WiFiScanResult > scan_result_
void save_fast_connect_settings_(const bssid_t &bssid, uint8_t channel)
bool skip_cooldown_next_cycle_
WiFiPowerSaveMode configured_power_save_
void set_sta_priority(bssid_t bssid, int8_t priority)
StaticVector< WiFiScanResultsListener *, ESPHOME_WIFI_SCAN_RESULTS_LISTENERS > scan_results_listeners_
void print_connect_params_()
void loop() override
Reconnect WiFi if required.
void notify_ip_state_listeners_()
Notify IP state listeners with current addresses.
bool is_scan_driver_filtered_() const
void start_connecting(const WiFiAP &ap)
void advance_to_next_target_or_increment_retry_()
Advance to next target (AP/SSID) within current phase, or increment retry counter Called when staying...
void check_roaming_(uint32_t now)
static constexpr uint32_t ROAMING_CHECK_INTERVAL
RoamingState roaming_state_
void check_scanning_finished()
void process_roaming_scan_()
SemaphoreHandle_t high_performance_semaphore_
network::IPAddress get_dns_address(int num)
void pause_exclusive_ap_()
Drop the fallback AP so the networks can be tried; it comes back after ap_timeout.
WiFiComponent()
Construct a WiFiComponent.
wifi_band_mode_t band_mode_
std::vector< WiFiSTAPriority > sta_priorities_
bssid_t roaming_target_bssid_
int8_t selected_sta_index_
static constexpr int8_t ROAMING_GOOD_RSSI
void notify_disconnect_state_listeners_()
Notify connect state listeners of disconnection.
bool wifi_start_ap_(const WiFiAP &ap)
StaticVector< WiFiConnectStateListener *, ESPHOME_WIFI_CONNECT_STATE_LISTENERS > connect_state_listeners_
void log_discarded_scan_result_(const char *ssid, const uint8_t *bssid, int8_t rssi, uint8_t channel)
Log a discarded scan result at VERBOSE level (skipped during roaming scans to avoid log overflow)
int32_t get_wifi_channel()
const char * wifi_ssid_to(std::span< char, SSID_BUFFER_SIZE > buffer)
Write SSID to buffer without heap allocation.
network::IPAddress wifi_subnet_mask_()
static constexpr uint8_t ROAMING_MAX_ATTEMPTS
network::IPAddress wifi_soft_ap_ip()
uint8_t roaming_attempts_
network::IPAddress wifi_gateway_ip_()
void set_power_save_mode(WiFiPowerSaveMode power_save)
bool is_roaming_scan_active() const
True while a post-connect roaming scan holds the radio off-channel.
int8_t find_next_hidden_sta_(int8_t start_index)
Find next SSID that wasn't in scan results (might be hidden) Returns index of next potentially hidden...
ESPPreferenceObject fast_connect_pref_
void clear_priorities_if_all_min_()
Clear BSSID priority tracking if all priorities are at minimum (saves memory)
static constexpr uint32_t ROAMING_SCAN_GRACE_PERIOD
WiFiRetryPhase determine_next_phase_()
Determine next retry phase based on current state and failure conditions.
network::IPAddress wifi_dns_ip_(int num)
bool wifi_apply_hostname_()
bool wifi_sta_pre_setup_()
network::IPAddresses get_ip_addresses()
static constexpr int8_t ROAMING_MIN_IMPROVEMENT
bool matches_configured_network_(const char *ssid, const uint8_t *bssid) const
Check if network matches any configured network (for scan result filtering) Matches by SSID when conf...
struct esphome::wifi::WiFiComponent::@194 pending_
void disable_ap_()
End the captive portal and turn the AP off.
StaticVector< WiFiIPStateListener *, ESPHOME_WIFI_IP_STATE_LISTENERS > ip_state_listeners_
FixedVector< WiFiAP > sta_
int8_t find_first_non_hidden_index_() const
Find the index of the first non-hidden network Returns where EXPLICIT_HIDDEN phase would have stopped...
void release_scan_results_()
Free scan results memory unless a component needs them.
bool needs_scan_results_() const
Check if we need valid scan results for the current phase but don't have any Returns true if the phas...
bool is_improv_ble_active_()
void update_connected_state_()
WiFiRetryPhase retry_phase_
bool transition_to_phase_(WiFiRetryPhase new_phase)
Transition to a new retry phase with logging Returns true if a scan was started (caller should wait),...
bool needs_full_scan_results_() const
Check if full scan results are needed (captive portal active, improv, listeners)
WiFiAP build_params_for_current_phase_()
bool is_high_performance_mode_
void dump_config() override
static constexpr uint8_t FIRST_5GHZ_CHANNEL
void clear_roaming_state_()
bool release_high_performance()
Release a high-performance mode request.
RetryHiddenMode retry_hidden_mode_
void force_roam_check()
Force an immediate post-connect roaming check, bypassing the periodic interval and the per-connection...
bool wifi_apply_output_power_(float output_power)
bool has_completed_scan_after_captive_portal_start_
bool is_connected() const
bool wifi_sta_connect_(const WiFiAP &ap)
bool went_through_explicit_hidden_phase_() const
Check if we went through EXPLICIT_HIDDEN phase (first network is marked hidden) Used in RETRY_HIDDEN ...
bool wifi_mode_(optional< bool > sta, optional< bool > ap)
uint32_t roaming_scan_end_
network::IPAddresses wifi_sta_ip_addresses()
void check_connecting_finished(uint32_t now)
void start_initial_connection_()
Start initial connection - either scan or connect directly to hidden networks.
bool wifi_scan_start_(bool passive)
bool ssid_was_seen_in_scan_(const CompactString &ssid) const
Check if an SSID was seen in the most recent scan results Used to skip hidden mode for SSIDs we know ...
bool handled_connected_state_
void handle_driver_roam_(const bssid_t &bssid, uint8_t channel)
Redo post-connect bookkeeping after a driver-initiated roam (e.g.
void setup() override
Setup WiFi interface.
void clear_all_bssid_priorities_()
Clear all BSSID priority penalties after successful connection (stale after disconnect)
bool post_connect_roaming_
Trigger disconnect_trigger_
int8_t get_priority() const
uint8_t get_channel() const
WiFiScanResult(const bssid_t &bssid, const char *ssid, size_t ssid_len, uint8_t channel, int8_t rssi, bool with_auth, bool is_hidden)
const bssid_t & get_bssid() const
StringRef get_ssid() const
bool matches(const WiFiAP &config) const
bool get_is_hidden() const
bool operator==(const WiFiScanResult &rhs) const
struct @66::@67 __attribute__
Wake the main loop task from an ISR. ISR-safe.
CaptivePortal * global_captive_portal
ImprovBLEComponent * global_improv_component
ImprovSerialComponent * global_improv_serial_component
std::array< IPAddress, 5 > IPAddresses
ProvisioningManager * global_provisioning_manager
std::array< uint8_t, 6 > bssid_t
const LogString * get_signal_bars(int8_t rssi)
@ BLIND_RETRY
Blind retry mode: scanning disabled (captive portal/improv active), try ALL configured networks seque...
@ SCAN_BASED
Normal mode: scan completed, only try networks NOT visible in scan results (truly hidden networks tha...
@ ERROR_NETWORK_NOT_FOUND
WiFiRetryPhase
Tracks the current retry strategy/phase for WiFi connection attempts.
@ RETRY_HIDDEN
Retry networks not found in scan (might be hidden)
@ RESTARTING_ADAPTER
Restarting WiFi adapter to clear stuck state.
@ INITIAL_CONNECT
Initial connection attempt (varies based on fast_connect setting)
@ EXPLICIT_HIDDEN
Explicitly hidden networks (user marked as hidden, try before scanning)
@ FAST_CONNECT_CYCLING_APS
Fast connect mode: cycling through configured APs (config-only, no scan)
@ SCAN_CONNECTING
Scan-based: connecting to best AP from scan results.
WiFiComponent * global_wifi_component
@ SCANNING
Scanning for better AP.
@ CONNECTING
Attempting to connect to better AP found in scan.
@ IDLE
Not roaming, waiting for next check interval.
@ RECONNECTING
Roam connection failed, reconnecting to any available AP.
@ WIFI_COMPONENT_STATE_DISABLED
WiFi is disabled.
@ WIFI_COMPONENT_STATE_AP
WiFi is in AP-only mode and internal AP is already enabled.
@ WIFI_COMPONENT_STATE_STA_CONNECTING
WiFi is in STA(+AP) mode and currently connecting to an AP.
@ WIFI_COMPONENT_STATE_OFF
Nothing has been initialized yet.
@ WIFI_COMPONENT_STATE_STA_SCANNING
WiFi is in STA-only mode and currently scanning for APs.
@ WIFI_COMPONENT_STATE_COOLDOWN
WiFi is in cooldown mode because something went wrong, scanning will begin after a short period of ti...
@ WIFI_COMPONENT_STATE_STA_CONNECTED
WiFi is in STA(+AP) mode and successfully connected.
uint16_t uint16_t size_t elem_size
ESPPreferences * global_preferences
const char * get_mac_address_pretty_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in colon-separated uppercase hex notation.
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
ESPPreferenceObject make_preference(size_t, uint32_t, bool)
bool sync()
Commit pending writes to flash.
esp_eap_ttls_phase2_types ttls_phase_2
Struct for setting static IPs in WiFiComponent.