ESPHome 2026.7.3
Loading...
Searching...
No Matches
wifi_component.h
Go to the documentation of this file.
1#pragma once
2
4#ifdef USE_WIFI
9#ifdef USE_ESP32
11#endif
12#if defined(USE_LIBRETINY) && defined(ESPHOME_THREAD_MULTI_ATOMICS)
14#elif defined(USE_LIBRETINY) && defined(ESPHOME_THREAD_MULTI_NO_ATOMICS)
16#endif
18
19#include <atomic>
20#include <limits>
21#include <span>
22#include <string>
23#include <type_traits>
24#include <vector>
25
26#ifdef USE_LIBRETINY
27#include <WiFi.h>
28#endif
29
30#if defined(USE_ESP32) && defined(USE_WIFI_WPA2_EAP)
31#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
32#include <esp_eap_client.h>
33#else
34#include <esp_wpa2.h>
35#endif
36#endif
37
38#ifdef USE_ESP8266
39#include <ESP8266WiFi.h>
40#include <ESP8266WiFiType.h>
41
42#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0)
43extern "C" {
44#include <user_interface.h>
45};
46#endif
47#endif
48
49#ifdef USE_RP2
50extern "C" {
51#include "cyw43.h"
52#include "cyw43_country.h"
53#include "pico/cyw43_arch.h"
54}
55
56#include <WiFi.h>
57#endif
58
59#if defined(USE_ESP32) && defined(SOC_WIFI_SUPPORT_5G)
60#include <esp_wifi_types.h>
61#endif
62
63#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
64#include <freertos/FreeRTOS.h>
65#include <freertos/semphr.h>
66#endif
67
68namespace esphome::wifi {
69
71static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127;
72
74static constexpr size_t SSID_BUFFER_SIZE = 33;
75
77 char ssid[33];
78 char password[65];
79} PACKED; // NOLINT
80
82 uint8_t bssid[6];
83 uint8_t channel;
84 int8_t ap_index;
85} PACKED; // NOLINT
86
103
111
113enum class WiFiRetryPhase : uint8_t {
116#ifdef USE_WIFI_FAST_CONNECT
119#endif
128};
129
131enum class RoamingState : uint8_t {
133 IDLE,
135 SCANNING,
140};
141
143enum class RetryHiddenMode : uint8_t {
150};
151
160
161#ifdef USE_WIFI_WPA2_EAP
162struct EAPAuth {
163 std::string identity; // required for all auth types
164 std::string username;
165 std::string password;
166 const char *ca_cert; // optionally verify authentication server
167 // used for EAP-TLS
168 const char *client_cert;
169 const char *client_key;
170// used for EAP-TTLS
171#ifdef USE_ESP32
172 esp_eap_ttls_phase2_types ttls_phase_2;
173#endif
174};
175#endif // USE_WIFI_WPA2_EAP
176
177using bssid_t = std::array<uint8_t, 6>;
178
180static constexpr size_t WIFI_SCAN_RESULT_FILTERED_RESERVE = 8;
181
182// Use std::vector for RP2040 (callback-based) and ESP32 (destructive scan API)
183// Use FixedVector for ESP8266 and LibreTiny where two-pass exact allocation is possible
184#if defined(USE_RP2) || defined(USE_ESP32)
185template<typename T> using wifi_scan_vector_t = std::vector<T>;
186#else
187template<typename T> using wifi_scan_vector_t = FixedVector<T>;
188#endif
189
190// A consumer component (e.g. the captive portal) reads scan results from another
191// task; guard them with a real lock only on platforms that actually run multiple
192// threads. See ScanResultsLock below the WiFiComponent class.
193#if defined(USE_WIFI_SCAN_RESULTS_LOCK) && !defined(ESPHOME_THREAD_SINGLE)
194#define WIFI_SCAN_RESULTS_LOCK_ENABLED
195#endif
196
200 public:
201 static constexpr uint8_t MAX_LENGTH = 127;
202 static constexpr uint8_t INLINE_CAPACITY = 18; // 18 chars + null terminator fits in 19 bytes
203
204 CompactString() : length_(0), is_heap_(0) { this->storage_[0] = '\0'; }
205 CompactString(const char *str, size_t len);
206 CompactString(const CompactString &other);
207 CompactString(CompactString &&other) noexcept;
209 CompactString &operator=(CompactString &&other) noexcept;
211
212 const char *data() const { return this->is_heap_ ? this->get_heap_ptr_() : this->storage_; }
213 const char *c_str() const { return this->data(); } // Always null-terminated
214 size_t size() const { return this->length_; }
215 bool empty() const { return this->length_ == 0; }
216
218 StringRef ref() const { return StringRef(this->data(), this->size()); }
219
220 bool operator==(const CompactString &other) const;
221 bool operator!=(const CompactString &other) const { return !(*this == other); }
222 bool operator==(const StringRef &other) const;
223 bool operator!=(const StringRef &other) const { return !(*this == other); }
224 bool operator==(const char *other) const { return *this == StringRef(other); }
225 bool operator!=(const char *other) const { return !(*this == other); }
226
227 protected:
228 char *get_heap_ptr_() const {
229 char *ptr;
230 std::memcpy(&ptr, this->storage_, sizeof(ptr));
231 return ptr;
232 }
233 void set_heap_ptr_(char *ptr) { std::memcpy(this->storage_, &ptr, sizeof(ptr)); }
234
235 // Storage for string data. When is_heap_=0, contains the string directly (null-terminated).
236 // When is_heap_=1, first sizeof(char*) bytes contain pointer to heap allocation.
237 char storage_[INLINE_CAPACITY + 1]; // 19 bytes: 18 chars + null terminator
238 uint8_t length_ : 7; // String length (0-127)
239 uint8_t is_heap_ : 1; // 1 if using heap pointer, 0 if using inline storage
240 // Total size: 20 bytes (19 bytes storage + 1 byte bitfields)
241};
242
243static_assert(sizeof(CompactString) == 20, "CompactString must be exactly 20 bytes");
244// CompactString is not trivially copyable (non-trivial destructor/copy for heap case).
245// However, its layout has no self-referential pointers: storage_[] contains either inline
246// data or an external heap pointer — never a pointer to itself. This is unlike libstdc++
247// std::string SSO where _M_p points to _M_local_buf within the same object.
248// This property allows memcpy-based permutation sorting where each element ends up in
249// exactly one slot (no ownership duplication). These asserts document that layout property.
250static_assert(std::is_standard_layout<CompactString>::value, "CompactString must be standard layout");
251static_assert(!std::is_polymorphic<CompactString>::value, "CompactString must not have vtable");
252
253class WiFiAP {
254 friend class WiFiComponent;
255 friend class WiFiScanResult;
256
257 public:
258 void set_ssid(const std::string &ssid);
259 void set_ssid(const char *ssid);
260 void set_ssid(StringRef ssid) { this->ssid_ = CompactString(ssid.c_str(), ssid.size()); }
261 void set_bssid(const bssid_t &bssid);
262 void clear_bssid();
263 void set_password(const std::string &password);
264 void set_password(const char *password);
265 void set_password(StringRef password) { this->password_ = CompactString(password.c_str(), password.size()); }
266#ifdef USE_WIFI_WPA2_EAP
267 void set_eap(optional<EAPAuth> eap_auth);
268#endif // USE_WIFI_WPA2_EAP
269 void set_channel(uint8_t channel);
270 void clear_channel();
272#ifdef USE_WIFI_MANUAL_IP
273 void set_manual_ip(optional<ManualIP> manual_ip);
274#endif
275 void set_hidden(bool hidden);
276 StringRef get_ssid() const { return this->ssid_.ref(); }
277 StringRef get_password() const { return this->password_.ref(); }
278 const bssid_t &get_bssid() const;
279 bool has_bssid() const;
280#ifdef USE_WIFI_WPA2_EAP
281 const optional<EAPAuth> &get_eap() const;
282#endif // USE_WIFI_WPA2_EAP
283 uint8_t get_channel() const { return this->channel_; }
284 bool has_channel() const { return this->channel_ != 0; }
285 int8_t get_priority() const { return priority_; }
286#ifdef USE_WIFI_MANUAL_IP
287 const optional<ManualIP> &get_manual_ip() const;
288#endif
289 bool get_hidden() const;
290
291 protected:
294#ifdef USE_WIFI_WPA2_EAP
295 optional<EAPAuth> eap_;
296#endif // USE_WIFI_WPA2_EAP
297#ifdef USE_WIFI_MANUAL_IP
298 optional<ManualIP> manual_ip_;
299#endif
300 // Group small types together to minimize padding
301 bssid_t bssid_{}; // 6 bytes, all zeros = any/not set
302 uint8_t channel_{0}; // 1 byte, 0 = auto/not set
303 int8_t priority_{0}; // 1 byte
304 bool hidden_{false}; // 1 byte (+ 3 bytes end padding to 4-byte align)
305};
306
308 friend class WiFiComponent;
309
310 public:
311 WiFiScanResult(const bssid_t &bssid, const char *ssid, size_t ssid_len, uint8_t channel, int8_t rssi, bool with_auth,
312 bool is_hidden);
313
314 bool matches(const WiFiAP &config) const;
315
316 bool get_matches() const;
317 void set_matches(bool matches);
318 const bssid_t &get_bssid() const;
319 StringRef get_ssid() const { return this->ssid_.ref(); }
320 uint8_t get_channel() const;
321 int8_t get_rssi() const;
322 bool get_with_auth() const;
323 bool get_is_hidden() const;
324 int8_t get_priority() const { return priority_; }
326
327 bool operator==(const WiFiScanResult &rhs) const;
328
329 protected:
331 uint8_t channel_;
332 int8_t rssi_;
334 int8_t priority_{0};
335 bool matches_{false};
338};
339
344
350
356
357#ifdef USE_WIFI_PHY_MODE
358// Values 1-3 match ESP8266 SDK phy_mode_t (PHY_MODE_11B=1, PHY_MODE_11G=2, PHY_MODE_11N=3).
359// AUTO leaves the SDK at its default (no wifi_set_phy_mode() call).
366#endif
367
368#ifdef USE_ESP32
369struct IDFWiFiEvent;
370#endif
371
372#ifdef USE_LIBRETINY
373struct LTWiFiEvent;
374#endif
375
385 public:
386 virtual void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
387 const network::IPAddress &dns2) = 0;
388};
389
399 public:
401};
402
412 public:
413 virtual void on_wifi_connect_state(StringRef ssid, std::span<const uint8_t, 6> bssid) = 0;
414};
415
425 public:
427};
428
430class WiFiComponent final : public Component {
431 public:
434
435 void set_sta(const WiFiAP &ap);
436 // Returns a copy of the currently selected AP configuration
437 WiFiAP get_sta() const;
438 void init_sta(size_t count);
439 void add_sta(const WiFiAP &ap);
440 void clear_sta();
441
442#ifdef USE_WIFI_AP
450 void set_ap(const WiFiAP &ap);
451 WiFiAP get_ap() { return this->ap_; }
452 void set_ap_timeout(uint32_t ap_timeout) { ap_timeout_ = ap_timeout; }
453#endif // USE_WIFI_AP
454
455 void enable();
456 void disable();
457 bool is_disabled();
458 void start_scanning();
460 void start_connecting(const WiFiAP &ap);
461 // Backward compatibility overload - ignores 'two' parameter
462 void start_connecting(const WiFiAP &ap, bool /* two */) { this->start_connecting(ap); }
463
465
466 void retry_connect();
467
468 void set_reboot_timeout(uint32_t reboot_timeout);
469
470 bool is_connected() const { return this->connected_; }
471
472 void set_power_save_mode(WiFiPowerSaveMode power_save);
473 void set_min_auth_mode(WifiMinAuthMode min_auth_mode) { min_auth_mode_ = min_auth_mode; }
474 void set_output_power(float output_power) { output_power_ = output_power; }
475#if defined(USE_ESP32) && defined(SOC_WIFI_SUPPORT_5G)
476 void set_band_mode(wifi_band_mode_t band_mode) { this->band_mode_ = band_mode; }
477#endif
478#ifdef USE_WIFI_PHY_MODE
479 void set_phy_mode(WiFi8266PhyMode phy_mode) { this->phy_mode_ = phy_mode; }
480#endif
481
482 void set_passive_scan(bool passive);
483
484 void save_wifi_sta(const std::string &ssid, const std::string &password);
485 void save_wifi_sta(const char *ssid, const char *password);
486 void save_wifi_sta(StringRef ssid, StringRef password) { this->save_wifi_sta(ssid.c_str(), password.c_str()); }
487
488 // ========== INTERNAL METHODS ==========
489 // (In most use cases you won't need these)
491 void setup() override;
492 void start();
493 void dump_config() override;
494 void restart_adapter();
496 float get_setup_priority() const override;
498 void loop() override;
499
500 bool has_sta() const { return !this->sta_.empty(); }
501 bool has_ap() const { return this->has_ap_; }
502 bool is_ap_active() const { return this->ap_started_; }
503
504#ifdef USE_WIFI_11KV_SUPPORT
505 void set_btm(bool btm);
506 void set_rrm(bool rrm);
507#endif
508
513 const char *get_use_address() const { return this->use_address_; }
514 void set_use_address(const char *use_address) { this->use_address_ = use_address; }
515
520
522
523 bool has_sta_priority(const bssid_t &bssid) {
524 for (auto &it : this->sta_priorities_) {
525 if (it.bssid == bssid)
526 return true;
527 }
528 return false;
529 }
530 int8_t get_sta_priority(const bssid_t bssid) {
531 for (auto &it : this->sta_priorities_) {
532 if (it.bssid == bssid)
533 return it.priority;
534 }
535 return 0;
536 }
537 void set_sta_priority(bssid_t bssid, int8_t priority);
538
540 // Remove before 2026.9.0
541 ESPDEPRECATED("Use wifi_ssid_to() instead. Removed in 2026.9.0", "2026.3.0")
542 std::string wifi_ssid();
545 const char *wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer);
547
548 int8_t wifi_rssi();
549
550 void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; }
551 void set_keep_scan_results(bool keep_scan_results) { this->keep_scan_results_ = keep_scan_results; }
552 void set_post_connect_roaming(bool enabled) { this->post_connect_roaming_ = enabled; }
553
554#ifdef USE_WIFI_CONNECT_TRIGGER
556#endif
557#ifdef USE_WIFI_DISCONNECT_TRIGGER
559#endif
560
561 int32_t get_wifi_channel();
562
563#ifdef USE_WIFI_IP_STATE_LISTENERS
567 void add_ip_state_listener(WiFiIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
568#endif // USE_WIFI_IP_STATE_LISTENERS
569#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
572 this->scan_results_listeners_.push_back(listener);
573 }
574#endif // USE_WIFI_SCAN_RESULTS_LISTENERS
575#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
580 this->connect_state_listeners_.push_back(listener);
581 }
582#endif // USE_WIFI_CONNECT_STATE_LISTENERS
583#ifdef USE_WIFI_POWER_SAVE_LISTENERS
587 void add_power_save_listener(WiFiPowerSaveListener *listener) { this->power_save_listeners_.push_back(listener); }
588#endif // USE_WIFI_POWER_SAVE_LISTENERS
589
590#ifdef USE_WIFI_RUNTIME_POWER_SAVE
606
619#endif // USE_WIFI_RUNTIME_POWER_SAVE
620
621#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_ROAMING_SUPPRESSION)
638 uint8_t current = this->roaming_suppression_count_.load(std::memory_order_relaxed);
639 // CAS loop: saturate at max instead of wrapping, so an excess of requests can't roll the
640 // counter back to zero and unintentionally re-enable roaming.
641 while (current < std::numeric_limits<uint8_t>::max() &&
642 !this->roaming_suppression_count_.compare_exchange_weak(current, current + 1, std::memory_order_relaxed)) {
643 }
644 }
645
655 uint8_t current = this->roaming_suppression_count_.load(std::memory_order_relaxed);
656 // CAS loop: decrement only if non-zero, so an unmatched release can't wrap the counter
657 // and permanently suppress roaming.
658 while (current > 0 &&
659 !this->roaming_suppression_count_.compare_exchange_weak(current, current - 1, std::memory_order_relaxed)) {
660 }
661 }
662#endif // USE_ESP32 && USE_WIFI_RUNTIME_ROAMING_SUPPRESSION
663
664 protected:
665#ifdef USE_WIFI_AP
666 void setup_ap_config_();
667#endif // USE_WIFI_AP
668
671
676 bool transition_to_phase_(WiFiRetryPhase new_phase);
679 bool needs_scan_results_() const;
685 int8_t find_first_non_hidden_index_() const;
688 bool ssid_was_seen_in_scan_(const CompactString &ssid) const;
690 bool needs_full_scan_results_() const;
693 bool matches_configured_network_(const char *ssid, const uint8_t *bssid) const;
695 void log_discarded_scan_result_(const char *ssid, const uint8_t *bssid, int8_t rssi, uint8_t channel);
699 int8_t find_next_hidden_sta_(int8_t start_index);
711 const WiFiAP *get_selected_sta_() const {
712 if (this->selected_sta_index_ >= 0 && static_cast<size_t>(this->selected_sta_index_) < this->sta_.size()) {
713 return &this->sta_[this->selected_sta_index_];
714 }
715 return nullptr;
716 }
717
719 if (this->selected_sta_index_ < 0 || static_cast<size_t>(this->selected_sta_index_) >= this->sta_.size()) {
720 this->selected_sta_index_ = this->sta_.empty() ? -1 : 0;
721 }
722 }
723
724 bool all_networks_hidden_() const {
725 if (this->sta_.empty())
726 return false;
727 for (const auto &ap : this->sta_) {
728 if (!ap.get_hidden())
729 return false;
730 }
731 return true;
732 }
733
734 void connect_soon_();
735
736 bool wifi_loop_();
737#ifdef USE_ESP8266
739#endif
740 bool wifi_mode_(optional<bool> sta, optional<bool> ap);
741 bool wifi_sta_pre_setup_();
742 bool wifi_apply_output_power_(float output_power);
744#if defined(USE_ESP32) && defined(SOC_WIFI_SUPPORT_5G)
746#endif
747#ifdef USE_WIFI_PHY_MODE
749#endif
750 bool wifi_sta_ip_config_(const optional<ManualIP> &manual_ip);
752 bool wifi_sta_connect_(const WiFiAP &ap);
753 void wifi_pre_setup_();
754#ifdef USE_ESP32
755 // ESP-IDF only: defers esp_wifi_init() + netif creation (which allocate ~15-30KB of
756 // DMA-capable internal SRAM) until wifi actually needs to come up. Idempotent.
757 // Called from setup() only when enable_on_boot_=true, and from enable() on first use.
759#endif
766 bool wifi_scan_start_(bool passive);
767
768#ifdef USE_WIFI_AP
769 bool wifi_ap_ip_config_(const optional<ManualIP> &manual_ip);
770 bool wifi_start_ap_(const WiFiAP &ap);
771#endif // USE_WIFI_AP
772
773 bool wifi_disconnect_();
774
778
781
782#ifdef USE_WIFI_FAST_CONNECT
785#endif
786
787 // Post-connect roaming methods
788 void check_roaming_(uint32_t now);
791
793 bool roaming_suppressed_() const {
794#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_ROAMING_SUPPRESSION)
795 return this->roaming_suppression_count_.load(std::memory_order_relaxed) != 0;
796#else
797 return false;
798#endif
799 }
800
803
804#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
809#endif
810#ifdef USE_WIFI_IP_STATE_LISTENERS
813#endif
814#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
817#endif
818
819#ifdef USE_ESP8266
820 static void wifi_event_callback(System_Event_t *event);
821 void wifi_scan_done_callback_(void *arg, STATUS status);
822 static void s_wifi_scan_done_callback(void *arg, STATUS status);
823#endif
824
825#ifdef USE_ESP32
826 void wifi_process_event_(IDFWiFiEvent *data);
827 friend void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
828#endif
829
830 friend class ScanResultsLock;
831
832#ifdef USE_RP2
833 static int s_wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result);
834 void wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result);
835#endif
836
837#ifdef USE_LIBRETINY
838 void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info);
839 void wifi_process_event_(LTWiFiEvent *event);
841#endif
842
843 // Large/pointer-aligned members first
845 std::vector<WiFiSTAPriority> sta_priorities_;
846 // Guarded by ScanResultsLock (see below this class)
848#ifdef WIFI_SCAN_RESULTS_LOCK_ENABLED
850#endif
851#ifdef USE_WIFI_AP
853#endif
854#ifdef USE_WIFI_IP_STATE_LISTENERS
856#endif
857#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
859#endif
860#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
862#endif
863#ifdef USE_WIFI_POWER_SAVE_LISTENERS
865#endif
867#ifdef USE_WIFI_FAST_CONNECT
869#endif
870#ifdef USE_WIFI_CONNECT_TRIGGER
872#endif
873#ifdef USE_WIFI_DISCONNECT_TRIGGER
875#endif
876#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
877 SemaphoreHandle_t high_performance_semaphore_{nullptr};
878#endif
879
880 static constexpr uint8_t FIRST_5GHZ_CHANNEL = 36;
881
882 // Post-connect roaming constants
883 static constexpr uint32_t ROAMING_CHECK_INTERVAL = 5 * 60 * 1000; // 5 minutes
884 static constexpr int8_t ROAMING_MIN_IMPROVEMENT = 10; // dB
885 static constexpr int8_t ROAMING_GOOD_RSSI = -49; // Skip scan if signal is excellent
886 static constexpr uint8_t ROAMING_MAX_ATTEMPTS = 3;
887 // Grace period after roaming scan completes. If WiFi disconnects within this
888 // window (e.g., ESP8266 Beacon Timeout caused by going off-channel during scan),
889 // the disconnect is treated as roaming-related and the attempts counter is preserved.
890 static constexpr uint32_t ROAMING_SCAN_GRACE_PERIOD = 30 * 1000; // 30 seconds
891
892 // 4-byte members
893 float output_power_{NAN};
898 uint32_t roaming_scan_end_{0}; // Timestamp when last roaming scan completed
899#ifdef USE_WIFI_AP
901#endif
902
903 // 1-byte enums and integers
906#if defined(USE_ESP32) && defined(SOC_WIFI_SUPPORT_5G)
907 wifi_band_mode_t band_mode_{WIFI_BAND_MODE_AUTO};
908#endif
909#ifdef USE_WIFI_PHY_MODE
911#endif
914 uint8_t num_retried_{0};
915 // Index into sta_ array for the currently selected AP configuration (-1 = none selected)
916 // Used to access password, manual_ip, priority, EAP settings, and hidden flag
917 // int8_t limits to 127 APs (enforced in __init__.py via MAX_WIFI_NETWORKS)
920#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_ROAMING_SUPPRESSION)
921 // Count of active roaming-suppression requests. Incremented/decremented from any task
922 // (e.g. audio playback), read in loop(). Roaming scans are paused while non-zero.
923 // Relaxed ordering is sufficient: the count value is the only data shared across threads,
924 // so no happens-before relationship with other memory needs to be established.
925 std::atomic<uint8_t> roaming_suppression_count_{0};
926#endif
927#if USE_NETWORK_IPV6
929#endif /* USE_NETWORK_IPV6 */
931#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
932 // Platform-specific STA state enum, defined in platform cpp file.
933 // On ESP8266, written from SDK system context (wifi_event_callback) —
934 // uint8_t writes are atomic on Xtensa LX106 so no synchronization is needed.
935 uint8_t sta_state_{0};
936#endif
939 bssid_t roaming_target_bssid_{}; // BSSID of the AP we're trying to roam to
940#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
942#endif
943
944 // Bools and bitfields
945 // Pending listener callbacks deferred from platform callbacks to main loop.
946 struct {
947#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
948 // Deferred until state machine reaches STA_CONNECTED so wifi.connected
949 // condition returns true in listener automations.
951#ifdef USE_ESP8266
952 // ESP8266: also defer disconnect notification to main loop
953 bool disconnect : 1;
954#endif
955#endif
956#if defined(USE_ESP8266) && defined(USE_WIFI_IP_STATE_LISTENERS)
957 bool got_ip : 1;
958#endif
959#if defined(USE_ESP8266) && defined(USE_WIFI_SCAN_RESULTS_LISTENERS)
961#endif
963 bool has_ap_{false};
964#if defined(USE_WIFI_CONNECT_TRIGGER) || defined(USE_WIFI_DISCONNECT_TRIGGER)
966#endif
967 bool scan_done_{false};
968 bool ap_setup_{false};
969 bool ap_started_{false};
970 bool passive_scan_{false};
972#ifdef USE_WIFI_11KV_SUPPORT
973 bool btm_{false};
974 bool rrm_{false};
975#endif
976 bool enable_on_boot_{true};
977#ifdef USE_ESP32
978 // Tracks whether esp_wifi_init() + netif creation has happened. Allows enable()
979 // to be called at runtime without re-allocating, and ensures the heavy init is
980 // skipped entirely when enable_on_boot_ is false until first enable().
981 bool wifi_initialized_{false};
982#endif
983 bool got_ipv4_address_{false};
986 false}; // Tracks if we've completed a scan after captive portal started
988 bool connected_{false};
989 bool post_connect_roaming_{true}; // Enabled by default
990#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
992#endif
993
994#ifdef USE_ESP32
995 // Lock-free SPSC queue for WiFi events from ESP-IDF event handler.
996 // 17 slots = 16 usable (ring buffer reserves one slot). WiFi events are rare.
997 // Placed at end of class to avoid padding between smaller fields.
999#endif
1000
1001#ifdef USE_LIBRETINY
1002 // Thread-safe queue for WiFi events from LibreTiny callback thread.
1003 // LockFreeQueue on platforms with hardware atomics (RTL87xx, LN882x),
1004 // FreeRTOSQueue on platforms without (BK72xx).
1005 static constexpr uint8_t LT_EVENT_QUEUE_SIZE = 16;
1006#ifdef ESPHOME_THREAD_MULTI_ATOMICS
1007 // Ring buffer reserves one slot, so +1 for 16 usable slots
1009#else
1011#endif
1012#endif
1013
1014 private:
1015 // Stores a pointer to a string literal (static storage duration).
1016 // ONLY set from Python-generated code with string literals - never dynamic strings.
1017 const char *use_address_{nullptr};
1018};
1019
1020extern WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
1021
1031 public:
1032#ifdef WIFI_SCAN_RESULTS_LOCK_ENABLED
1033 ScanResultsLock(WiFiComponent *parent) : guard_(parent->scan_result_lock_) {}
1034
1035 private:
1036 LockGuard guard_;
1037#else
1039#endif
1040};
1041
1042} // namespace esphome::wifi
1043#endif
BedjetMode mode
BedJet operating mode.
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const std ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const char *name
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:534
Helper class that wraps a mutex with a RAII-style API.
Definition helpers.h:1937
Mutex implementation, with API based on the unavailable std::mutex.
Definition helpers.h:1898
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:222
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
constexpr const char * c_str() const
Definition string_ref.h:73
constexpr size_type size() const
Definition string_ref.h:74
20-byte string: 18 chars inline + null, heap for longer.
const char * data() const
StringRef ref() const
Return a StringRef view of this string (zero-copy)
bool operator!=(const CompactString &other) const
CompactString & operator=(const CompactString &other)
bool operator==(const CompactString &other) const
static constexpr uint8_t INLINE_CAPACITY
bool operator==(const char *other) const
bool operator!=(const StringRef &other) const
const char * c_str() const
char storage_[INLINE_CAPACITY+1]
static constexpr uint8_t MAX_LENGTH
bool operator!=(const char *other) const
Guards WiFiComponent::scan_result_.
ScanResultsLock(WiFiComponent *parent)
uint8_t get_channel() const
StringRef get_ssid() const
void set_ssid(const std::string &ssid)
const optional< EAPAuth > & get_eap() const
void set_ssid(StringRef ssid)
void set_bssid(const bssid_t &bssid)
void set_channel(uint8_t channel)
optional< EAPAuth > eap_
StringRef get_password() const
optional< ManualIP > manual_ip_
void set_eap(optional< EAPAuth > eap_auth)
void set_password(const std::string &password)
void set_manual_ip(optional< ManualIP > manual_ip)
const optional< ManualIP > & get_manual_ip() const
int8_t get_priority() const
void set_hidden(bool hidden)
const bssid_t & get_bssid() const
void set_priority(int8_t priority)
void set_password(StringRef password)
This component is responsible for managing the ESP WiFi interface.
void notify_scan_results_listeners_()
Notify scan results listeners with current scan results.
void add_sta(const WiFiAP &ap)
bool load_fast_connect_settings_(WiFiAP &params)
void add_connect_state_listener(WiFiConnectStateListener *listener)
Add a listener for WiFi connection state changes.
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.
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...
bool has_sta_priority(const bssid_t &bssid)
const WiFiAP * get_selected_sta_() const
WiFiSTAConnectStatus wifi_sta_connect_status_() const
void set_band_mode(wifi_band_mode_t band_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 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)
wifi_scan_vector_t< WiFiScanResult > scan_result_
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 loop() override
Reconnect WiFi if required.
void notify_ip_state_listeners_()
Notify IP state listeners with current addresses.
void start_connecting(const WiFiAP &ap)
std::atomic< uint8_t > roaming_suppression_count_
void set_enable_on_boot(bool enable_on_boot)
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 add_power_save_listener(WiFiPowerSaveListener *listener)
Add a listener for WiFi power save mode changes.
bool wifi_sta_ip_config_(const optional< ManualIP > &manual_ip)
static constexpr uint32_t ROAMING_CHECK_INTERVAL
static int s_wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result)
SemaphoreHandle_t high_performance_semaphore_
network::IPAddress get_dns_address(int num)
static void wifi_event_callback(System_Event_t *event)
WiFiComponent()
Construct a WiFiComponent.
void wifi_process_event_(IDFWiFiEvent *data)
std::vector< WiFiSTAPriority > sta_priorities_
struct esphome::wifi::WiFiComponent::@191 pending_
FreeRTOSQueue< LTWiFiEvent, LT_EVENT_QUEUE_SIZE > event_queue_
static constexpr int8_t ROAMING_GOOD_RSSI
void notify_disconnect_state_listeners_()
Notify connect state listeners of disconnection.
void set_min_auth_mode(WifiMinAuthMode min_auth_mode)
friend void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
StaticVector< WiFiConnectStateListener *, ESPHOME_WIFI_CONNECT_STATE_LISTENERS > connect_state_listeners_
void release_roaming_suppression()
Release a roaming suppression request.
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)
ESPDEPRECATED("Use wifi_ssid_to() instead. Removed in 2026.9.0", "2026.3.0") std const char * wifi_ssid_to(std::span< char, SSID_BUFFER_SIZE > buffer)
Write SSID to buffer without heap allocation.
void start_connecting(const WiFiAP &ap, bool)
static constexpr uint8_t ROAMING_MAX_ATTEMPTS
void set_passive_scan(bool passive)
void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info)
static void s_wifi_scan_done_callback(void *arg, STATUS status)
void set_power_save_mode(WiFiPowerSaveMode power_save)
LockFreeQueue< LTWiFiEvent, LT_EVENT_QUEUE_SIZE+1 > event_queue_
void set_phy_mode(WiFi8266PhyMode phy_mode)
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...
void add_ip_state_listener(WiFiIPStateListener *listener)
Add a listener for IP state changes.
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
void wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result)
WiFiRetryPhase determine_next_phase_()
Determine next retry phase based on current state and failure conditions.
network::IPAddress wifi_dns_ip_(int num)
network::IPAddresses get_ip_addresses()
static constexpr int8_t ROAMING_MIN_IMPROVEMENT
static constexpr uint8_t LT_EVENT_QUEUE_SIZE
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...
float get_setup_priority() const override
WIFI setup_priority.
void set_output_power(float output_power)
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.
void request_roaming_suppression()
Request that post-connect roaming scans be suppressed.
bool wifi_ap_ip_config_(const optional< ManualIP > &manual_ip)
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...
void add_scan_results_listener(WiFiScanResultsListener *listener)
Add a listener for WiFi scan results.
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)
static constexpr uint8_t FIRST_5GHZ_CHANNEL
LockFreeQueue< IDFWiFiEvent, 17 > event_queue_
void set_ap_timeout(uint32_t ap_timeout)
bool release_high_performance()
Release a high-performance mode request.
StaticVector< WiFiPowerSaveListener *, ESPHOME_WIFI_POWER_SAVE_LISTENERS > power_save_listeners_
bool wifi_apply_output_power_(float output_power)
void set_post_connect_roaming(bool enabled)
const char * get_use_address() const
Returns nullptr when no explicit use_address is configured and the address is derived at runtime from...
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)
void set_reboot_timeout(uint32_t reboot_timeout)
network::IPAddresses wifi_sta_ip_addresses()
void check_connecting_finished(uint32_t now)
void set_keep_scan_results(bool keep_scan_results)
void start_initial_connection_()
Start initial connection - either scan or connect directly to hidden networks.
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 ...
void save_wifi_sta(StringRef ssid, StringRef password)
void setup() override
Setup WiFi interface.
void clear_all_bssid_priorities_()
Clear all BSSID priority penalties after successful connection (stale after disconnect)
void set_use_address(const char *use_address)
const wifi_scan_vector_t< WiFiScanResult > & get_scan_result() const
Main-loop callers may read this directly.
Listener interface for WiFi connection state changes.
virtual void on_wifi_connect_state(StringRef ssid, std::span< const uint8_t, 6 > bssid)=0
Listener interface for WiFi IP state changes.
virtual void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, const network::IPAddress &dns2)=0
Listener interface for WiFi power save mode changes.
virtual void on_wifi_power_save(WiFiPowerSaveMode mode)=0
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
bool matches(const WiFiAP &config) const
void set_priority(int8_t priority)
bool operator==(const WiFiScanResult &rhs) const
Listener interface for WiFi scan results.
virtual void on_wifi_scan_results(const wifi_scan_vector_t< WiFiScanResult > &results)=0
uint8_t priority
std::array< IPAddress, 5 > IPAddresses
Definition ip_address.h:223
std::array< uint8_t, 6 > bssid_t
RetryHiddenMode
Controls how RETRY_HIDDEN phase selects networks to try.
@ 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...
std::vector< T > wifi_scan_vector_t
struct esphome::wifi::SavedWifiSettings PACKED
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
RoamingState
Tracks post-connect roaming state machine.
@ SCANNING
Scanning for better AP.
@ 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.
const void size_t len
Definition hal.h:64
STL namespace.
static void uint32_t
esp_eap_ttls_phase2_types ttls_phase_2
Struct for setting static IPs in WiFiComponent.
network::IPAddress static_ip
network::IPAddress dns1
The first DNS server. 0.0.0.0 for default.
network::IPAddress gateway
network::IPAddress dns2
The second DNS server. 0.0.0.0 for default.
network::IPAddress subnet
uint8_t event_id
Definition tt21100.cpp:3