ESPHome 2026.1.4
Loading...
Searching...
No Matches
wifi_component_esp8266.cpp
Go to the documentation of this file.
1#include "wifi_component.h"
3
4#ifdef USE_WIFI
5#ifdef USE_ESP8266
6
7#include <user_interface.h>
8
9#include <utility>
10#include <algorithm>
11#ifdef USE_WIFI_WPA2_EAP
12#include <wpa2_enterprise.h>
13#endif
14
15extern "C" {
16#include "lwip/err.h"
17#include "lwip/dns.h"
18#include "lwip/dhcp.h"
19#include "lwip/init.h" // LWIP_VERSION_
20#include "lwip/apps/sntp.h"
21#include "lwip/netif.h" // struct netif
22#include <AddrList.h>
23#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0)
24#include "LwipDhcpServer.h"
25#if USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0)
26#include <ESP8266WiFi.h>
27#include "ESP8266WiFiAP.h"
28#define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease)
29#define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time)
30#define wifi_softap_set_dhcps_offer_option(offer, mode) dhcpSoftAP.set_dhcps_offer_option(offer, mode)
31#endif
32#endif
33}
34
36#include "esphome/core/hal.h"
38#include "esphome/core/log.h"
39#include "esphome/core/util.h"
40
41namespace esphome::wifi {
42
43static const char *const TAG = "wifi_esp8266";
44
45static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
46static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
47static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
48static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
49static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
50
52 uint8_t current_mode = wifi_get_opmode();
53 bool current_sta = current_mode & 0b01;
54 bool current_ap = current_mode & 0b10;
55 bool target_sta = sta.value_or(current_sta);
56 bool target_ap = ap.value_or(current_ap);
57 if (current_sta == target_sta && current_ap == target_ap)
58 return true;
59
60 if (target_sta && !current_sta) {
61 ESP_LOGV(TAG, "Enabling STA");
62 } else if (!target_sta && current_sta) {
63 ESP_LOGV(TAG, "Disabling STA");
64 // Stop DHCP client when disabling STA
65 // See https://github.com/esp8266/Arduino/pull/5703
66 wifi_station_dhcpc_stop();
67 }
68 if (target_ap && !current_ap) {
69 ESP_LOGV(TAG, "Enabling AP");
70 } else if (!target_ap && current_ap) {
71 ESP_LOGV(TAG, "Disabling AP");
72 }
73
74 ETS_UART_INTR_DISABLE();
75 uint8_t mode = 0;
76 if (target_sta)
77 mode |= 0b01;
78 if (target_ap)
79 mode |= 0b10;
80 bool ret = wifi_set_opmode_current(mode);
81 ETS_UART_INTR_ENABLE();
82
83 if (!ret) {
84 ESP_LOGW(TAG, "Set mode failed");
85 return false;
86 }
87
88 this->ap_started_ = target_ap;
89
90 return ret;
91}
93 sleep_type_t power_save;
94 switch (this->power_save_) {
96 power_save = LIGHT_SLEEP_T;
97 break;
99 power_save = MODEM_SLEEP_T;
100 break;
102 default:
103 power_save = NONE_SLEEP_T;
104 break;
105 }
106 wifi_fpm_auto_sleep_set_in_null_mode(1);
107 bool success = wifi_set_sleep_type(power_save);
108#ifdef USE_WIFI_POWER_SAVE_LISTENERS
109 if (success) {
110 for (auto *listener : this->power_save_listeners_) {
111 listener->on_wifi_power_save(this->power_save_);
112 }
113 }
114#endif
115 return success;
116}
117
118#if LWIP_VERSION_MAJOR != 1
119/*
120 lwip v2 needs to be notified of IP changes, see also
121 https://github.com/d-a-v/Arduino/blob/0e7d21e17144cfc5f53c016191daca8723e89ee8/libraries/ESP8266WiFi/src/ESP8266WiFiSTA.cpp#L251
122 */
123#undef netif_set_addr // need to call lwIP-v1.4 netif_set_addr()
124extern "C" {
125struct netif *eagle_lwip_getif(int netif_index);
126void netif_set_addr(struct netif *netif, const ip4_addr_t *ip, const ip4_addr_t *netmask, const ip4_addr_t *gw);
127};
128#endif
129
131 // enable STA
132 if (!this->wifi_mode_(true, {}))
133 return false;
134
135 enum dhcp_status dhcp_status = wifi_station_dhcpc_status();
136 if (!manual_ip.has_value()) {
137 // lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly,
138 // the built-in SNTP client has a memory leak in certain situations. Disable this feature.
139 // https://github.com/esphome/issues/issues/2299
140 sntp_servermode_dhcp(false);
141
142 // Use DHCP client
143 if (dhcp_status != DHCP_STARTED) {
144 bool ret = wifi_station_dhcpc_start();
145 if (!ret) {
146 ESP_LOGV(TAG, "Starting DHCP client failed");
147 }
148 return ret;
149 }
150 return true;
151 }
152
153 bool ret = true;
154
155#if LWIP_VERSION_MAJOR != 1
156 // get current->previous IP address
157 // (check below)
158 ip_info previp{};
159 wifi_get_ip_info(STATION_IF, &previp);
160#endif
161
162 struct ip_info info {};
163 info.ip = manual_ip->static_ip;
164 info.gw = manual_ip->gateway;
165 info.netmask = manual_ip->subnet;
166
167 if (dhcp_status == DHCP_STARTED) {
168 bool dhcp_stop_ret = wifi_station_dhcpc_stop();
169 if (!dhcp_stop_ret) {
170 ESP_LOGV(TAG, "Stopping DHCP client failed");
171 ret = false;
172 }
173 }
174 bool wifi_set_info_ret = wifi_set_ip_info(STATION_IF, &info);
175 if (!wifi_set_info_ret) {
176 ESP_LOGV(TAG, "Set manual IP info failed");
177 ret = false;
178 }
179
180 ip_addr_t dns;
181 if (manual_ip->dns1.is_set()) {
182 dns = manual_ip->dns1;
183 dns_setserver(0, &dns);
184 }
185 if (manual_ip->dns2.is_set()) {
186 dns = manual_ip->dns2;
187 dns_setserver(1, &dns);
188 }
189
190#if LWIP_VERSION_MAJOR != 1
191 // trigger address change by calling lwIP-v1.4 api
192 // only when ip is already set by other mean (generally dhcp)
193 if (previp.ip.addr != 0 && previp.ip.addr != info.ip.addr) {
194 netif_set_addr(eagle_lwip_getif(STATION_IF), reinterpret_cast<const ip4_addr_t *>(&info.ip),
195 reinterpret_cast<const ip4_addr_t *>(&info.netmask), reinterpret_cast<const ip4_addr_t *>(&info.gw));
196 }
197#endif
198 return ret;
199}
200
202 if (!this->has_sta())
203 return {};
204 network::IPAddresses addresses;
205 uint8_t index = 0;
206 for (auto &addr : addrList) {
207 addresses[index++] = addr.ipFromNetifNum();
208 }
209 return addresses;
210}
212 const std::string &hostname = App.get_name();
213 bool ret = wifi_station_set_hostname(const_cast<char *>(hostname.c_str()));
214 if (!ret) {
215 ESP_LOGV(TAG, "Set hostname failed");
216 }
217
218 // inform dhcp server of hostname change using dhcp_renew()
219 for (netif *intf = netif_list; intf; intf = intf->next) {
220 // unconditionally update all known interfaces
221#if LWIP_VERSION_MAJOR == 1
222 intf->hostname = (char *) wifi_station_get_hostname();
223#else
224 intf->hostname = wifi_station_get_hostname();
225#endif
226 if (netif_dhcp_data(intf) != nullptr) {
227 // renew already started DHCP leases
228 err_t lwipret = dhcp_renew(intf);
229 if (lwipret != ERR_OK) {
230 ESP_LOGW(TAG, "wifi_apply_hostname_(%s): lwIP error %d on interface %c%c (index %d)", intf->hostname,
231 (int) lwipret, intf->name[0], intf->name[1], intf->num);
232 ret = false;
233 }
234 }
235 }
236
237 return ret;
238}
239
241 // enable STA
242 if (!this->wifi_mode_(true, {}))
243 return false;
244
245 this->wifi_disconnect_();
246
247 struct station_config conf {};
248 memset(&conf, 0, sizeof(conf));
249 if (ap.get_ssid().size() > sizeof(conf.ssid)) {
250 ESP_LOGE(TAG, "SSID too long");
251 return false;
252 }
253 if (ap.get_password().size() > sizeof(conf.password)) {
254 ESP_LOGE(TAG, "Password too long");
255 return false;
256 }
257 memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
258 memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
259
260 if (ap.has_bssid()) {
261 conf.bssid_set = 1;
262 memcpy(conf.bssid, ap.get_bssid().data(), 6);
263 } else {
264 conf.bssid_set = 0;
265 }
266
267#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
268 if (ap.get_password().empty()) {
269 conf.threshold.authmode = AUTH_OPEN;
270 } else {
271 // Set threshold based on configured minimum auth mode
272 // Note: ESP8266 doesn't support WPA3
273 switch (this->min_auth_mode_) {
275 conf.threshold.authmode = AUTH_WPA_PSK;
276 break;
278 case WIFI_MIN_AUTH_MODE_WPA3: // Fall back to WPA2 for ESP8266
279 conf.threshold.authmode = AUTH_WPA2_PSK;
280 break;
281 }
282 }
283 conf.threshold.rssi = -127;
284#endif
285
286 ETS_UART_INTR_DISABLE();
287 bool ret = wifi_station_set_config_current(&conf);
288 ETS_UART_INTR_ENABLE();
289
290 if (!ret) {
291 ESP_LOGV(TAG, "Set Station config failed");
292 return false;
293 }
294
295#ifdef USE_WIFI_MANUAL_IP
296 if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
297 return false;
298 }
299#else
300 if (!this->wifi_sta_ip_config_({})) {
301 return false;
302 }
303#endif
304
305 // setup enterprise authentication if required
306#ifdef USE_WIFI_WPA2_EAP
307 if (ap.get_eap().has_value()) {
308 // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0.
309 EAPAuth eap = ap.get_eap().value();
310 ret = wifi_station_set_enterprise_identity((uint8_t *) eap.identity.c_str(), eap.identity.length());
311 if (ret) {
312 ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_identity failed: %d", ret);
313 }
314 int ca_cert_len = strlen(eap.ca_cert);
315 int client_cert_len = strlen(eap.client_cert);
316 int client_key_len = strlen(eap.client_key);
317 if (ca_cert_len) {
318 ret = wifi_station_set_enterprise_ca_cert((uint8_t *) eap.ca_cert, ca_cert_len + 1);
319 if (ret) {
320 ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_ca_cert failed: %d", ret);
321 }
322 }
323 // workout what type of EAP this is
324 // validation is not required as the config tool has already validated it
325 if (client_cert_len && client_key_len) {
326 // if we have certs, this must be EAP-TLS
327 ret = wifi_station_set_enterprise_cert_key((uint8_t *) eap.client_cert, client_cert_len + 1,
328 (uint8_t *) eap.client_key, client_key_len + 1,
329 (uint8_t *) eap.password.c_str(), eap.password.length());
330 if (ret) {
331 ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_cert_key failed: %d", ret);
332 }
333 } else {
334 // in the absence of certs, assume this is username/password based
335 ret = wifi_station_set_enterprise_username((uint8_t *) eap.username.c_str(), eap.username.length());
336 if (ret) {
337 ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_username failed: %d", ret);
338 }
339 ret = wifi_station_set_enterprise_password((uint8_t *) eap.password.c_str(), eap.password.length());
340 if (ret) {
341 ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_password failed: %d", ret);
342 }
343 }
344 ret = wifi_station_set_wpa2_enterprise_auth(true);
345 if (ret) {
346 ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed: %d", ret);
347 }
348 }
349#endif // USE_WIFI_WPA2_EAP
350
351 this->wifi_apply_hostname_();
352
353 // Reset flags, do this _before_ wifi_station_connect as the callback method
354 // may be called from wifi_station_connect
355 s_sta_connecting = true;
356 s_sta_connected = false;
357 s_sta_got_ip = false;
358 s_sta_connect_error = false;
359 s_sta_connect_not_found = false;
360
361 ETS_UART_INTR_DISABLE();
362 ret = wifi_station_connect();
363 ETS_UART_INTR_ENABLE();
364 if (!ret) {
365 ESP_LOGV(TAG, "wifi_station_connect failed");
366 return false;
367 }
368
369#if USE_NETWORK_IPV6
370 bool connected = false;
371 while (!connected) {
372 uint8_t ipv6_addr_count = 0;
373 for (auto addr : addrList) {
374 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
375 ESP_LOGV(TAG, "Address %s", network::IPAddress(addr.ipFromNetifNum()).str_to(ip_buf));
376 if (addr.isV6()) {
377 ipv6_addr_count++;
378 }
379 }
380 delay(500); // NOLINT
381 connected = (ipv6_addr_count >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
382 }
383#endif /* USE_NETWORK_IPV6 */
384
385 if (ap.has_channel()) {
386 ret = wifi_set_channel(ap.get_channel());
387 if (!ret) {
388 ESP_LOGV(TAG, "wifi_set_channel failed");
389 return false;
390 }
391 }
392
393 return true;
394}
395
396class WiFiMockClass : public ESP8266WiFiGenericClass {
397 public:
398 static void _event_callback(void *event) { ESP8266WiFiGenericClass::_eventCallback(event); } // NOLINT
399};
400
401const LogString *get_auth_mode_str(uint8_t mode) {
402 switch (mode) {
403 case AUTH_OPEN:
404 return LOG_STR("OPEN");
405 case AUTH_WEP:
406 return LOG_STR("WEP");
407 case AUTH_WPA_PSK:
408 return LOG_STR("WPA PSK");
409 case AUTH_WPA2_PSK:
410 return LOG_STR("WPA2 PSK");
411 case AUTH_WPA_WPA2_PSK:
412 return LOG_STR("WPA/WPA2 PSK");
413 default:
414 return LOG_STR("UNKNOWN");
415 }
416}
417const LogString *get_op_mode_str(uint8_t mode) {
418 switch (mode) {
419 case WIFI_OFF:
420 return LOG_STR("OFF");
421 case WIFI_STA:
422 return LOG_STR("STA");
423 case WIFI_AP:
424 return LOG_STR("AP");
425 case WIFI_AP_STA:
426 return LOG_STR("AP+STA");
427 default:
428 return LOG_STR("UNKNOWN");
429 }
430}
431
432const LogString *get_disconnect_reason_str(uint8_t reason) {
433 /* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the
434 * REASON_* constants aren't continuous, and GCC will fill in the gap with the default value -- wasting 4 bytes of RAM
435 * per entry. As there's ~175 default entries, this wastes 700 bytes of RAM.
436 */
437 if (reason <= REASON_CIPHER_SUITE_REJECTED) { // This must be the last constant with a value <200
438 switch (reason) {
439 case REASON_AUTH_EXPIRE:
440 return LOG_STR("Auth Expired");
441 case REASON_AUTH_LEAVE:
442 return LOG_STR("Auth Leave");
443 case REASON_ASSOC_EXPIRE:
444 return LOG_STR("Association Expired");
445 case REASON_ASSOC_TOOMANY:
446 return LOG_STR("Too Many Associations");
447 case REASON_NOT_AUTHED:
448 return LOG_STR("Not Authenticated");
449 case REASON_NOT_ASSOCED:
450 return LOG_STR("Not Associated");
451 case REASON_ASSOC_LEAVE:
452 return LOG_STR("Association Leave");
453 case REASON_ASSOC_NOT_AUTHED:
454 return LOG_STR("Association not Authenticated");
455 case REASON_DISASSOC_PWRCAP_BAD:
456 return LOG_STR("Disassociate Power Cap Bad");
457 case REASON_DISASSOC_SUPCHAN_BAD:
458 return LOG_STR("Disassociate Supported Channel Bad");
459 case REASON_IE_INVALID:
460 return LOG_STR("IE Invalid");
461 case REASON_MIC_FAILURE:
462 return LOG_STR("Mic Failure");
463 case REASON_4WAY_HANDSHAKE_TIMEOUT:
464 return LOG_STR("4-Way Handshake Timeout");
465 case REASON_GROUP_KEY_UPDATE_TIMEOUT:
466 return LOG_STR("Group Key Update Timeout");
467 case REASON_IE_IN_4WAY_DIFFERS:
468 return LOG_STR("IE In 4-Way Handshake Differs");
469 case REASON_GROUP_CIPHER_INVALID:
470 return LOG_STR("Group Cipher Invalid");
471 case REASON_PAIRWISE_CIPHER_INVALID:
472 return LOG_STR("Pairwise Cipher Invalid");
473 case REASON_AKMP_INVALID:
474 return LOG_STR("AKMP Invalid");
475 case REASON_UNSUPP_RSN_IE_VERSION:
476 return LOG_STR("Unsupported RSN IE version");
477 case REASON_INVALID_RSN_IE_CAP:
478 return LOG_STR("Invalid RSN IE Cap");
479 case REASON_802_1X_AUTH_FAILED:
480 return LOG_STR("802.1x Authentication Failed");
481 case REASON_CIPHER_SUITE_REJECTED:
482 return LOG_STR("Cipher Suite Rejected");
483 }
484 }
485
486 switch (reason) {
487 case REASON_BEACON_TIMEOUT:
488 return LOG_STR("Beacon Timeout");
489 case REASON_NO_AP_FOUND:
490 return LOG_STR("AP Not Found");
491 case REASON_AUTH_FAIL:
492 return LOG_STR("Authentication Failed");
493 case REASON_ASSOC_FAIL:
494 return LOG_STR("Association Failed");
495 case REASON_HANDSHAKE_TIMEOUT:
496 return LOG_STR("Handshake Failed");
497 case REASON_UNSPECIFIED:
498 default:
499 return LOG_STR("Unspecified");
500 }
501}
502
503// TODO: This callback runs in ESP8266 system context with limited stack (~2KB).
504// All listener notifications should be deferred to wifi_loop_() via pending_ flags
505// to avoid stack overflow. Currently only connect_state is deferred; disconnect,
506// IP, and scan listeners still run in this context and should be migrated.
507void WiFiComponent::wifi_event_callback(System_Event_t *event) {
508 switch (event->event) {
509 case EVENT_STAMODE_CONNECTED: {
510 auto it = event->event_info.connected;
511#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
512 char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
513 format_mac_addr_upper(it.bssid, bssid_buf);
514 ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid, bssid_buf,
515 it.channel);
516#endif
517 s_sta_connected = true;
518#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
519 // Defer listener notification until state machine reaches STA_CONNECTED
520 // This ensures wifi.connected condition returns true in listener automations
522#endif
523 // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
524#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
525 if (const WiFiAP *config = global_wifi_component->get_selected_sta_();
526 config && config->get_manual_ip().has_value()) {
527 for (auto *listener : global_wifi_component->ip_state_listeners_) {
528 listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(),
530 }
531 }
532#endif
533 break;
534 }
535 case EVENT_STAMODE_DISCONNECTED: {
536 auto it = event->event_info.disconnected;
537 if (it.reason == REASON_NO_AP_FOUND) {
538 ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
539 (const char *) it.ssid);
540 s_sta_connect_not_found = true;
541 } else {
542 char bssid_s[18];
543 format_mac_addr_upper(it.bssid, bssid_s);
544 ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
545 (const char *) it.ssid, bssid_s, LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
546 s_sta_connect_error = true;
547 }
548 s_sta_connected = false;
549 s_sta_connecting = false;
550 // IMPORTANT: Set error flag BEFORE notifying listeners.
551 // This ensures is_connected() returns false during listener callbacks,
552 // which is critical for proper reconnection logic (e.g., roaming).
554#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
555 // Notify listeners AFTER setting error flag so they see correct state
556 static constexpr uint8_t EMPTY_BSSID[6] = {};
557 for (auto *listener : global_wifi_component->connect_state_listeners_) {
558 listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID);
559 }
560#endif
561 break;
562 }
563 case EVENT_STAMODE_AUTHMODE_CHANGE: {
564 auto it = event->event_info.auth_change;
565 ESP_LOGV(TAG, "Changed Authmode old=%s new=%s", LOG_STR_ARG(get_auth_mode_str(it.old_mode)),
566 LOG_STR_ARG(get_auth_mode_str(it.new_mode)));
567 // Mitigate CVE-2020-12638
568 // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors
569 if (it.old_mode != AUTH_OPEN && it.new_mode == AUTH_OPEN) {
570 ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting");
571 // we can't call retry_connect() from this context, so disconnect immediately
572 // and notify main thread with error_from_callback_
573 wifi_station_disconnect();
575 }
576 break;
577 }
578 case EVENT_STAMODE_GOT_IP: {
579 auto it = event->event_info.got_ip;
580 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE],
581 mask_buf[network::IP_ADDRESS_BUFFER_SIZE];
582 ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", network::IPAddress(&it.ip).str_to(ip_buf),
583 network::IPAddress(&it.gw).str_to(gw_buf), network::IPAddress(&it.mask).str_to(mask_buf));
584 s_sta_got_ip = true;
585#ifdef USE_WIFI_IP_STATE_LISTENERS
586 for (auto *listener : global_wifi_component->ip_state_listeners_) {
589 }
590#endif
591 break;
592 }
593 case EVENT_STAMODE_DHCP_TIMEOUT: {
594 ESP_LOGW(TAG, "DHCP request timeout");
595 break;
596 }
597 case EVENT_SOFTAPMODE_STACONNECTED: {
598#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
599 auto it = event->event_info.sta_connected;
600 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
601 format_mac_addr_upper(it.mac, mac_buf);
602 ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", mac_buf, it.aid);
603#endif
604 break;
605 }
606 case EVENT_SOFTAPMODE_STADISCONNECTED: {
607#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
608 auto it = event->event_info.sta_disconnected;
609 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
610 format_mac_addr_upper(it.mac, mac_buf);
611 ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", mac_buf, it.aid);
612#endif
613 break;
614 }
615 case EVENT_SOFTAPMODE_PROBEREQRECVED: {
616#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
617 auto it = event->event_info.ap_probereqrecved;
618 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
619 format_mac_addr_upper(it.mac, mac_buf);
620 ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi);
621#endif
622 break;
623 }
624#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
625 case EVENT_OPMODE_CHANGED: {
626 auto it = event->event_info.opmode_changed;
627 ESP_LOGV(TAG, "Changed Mode old=%s new=%s", LOG_STR_ARG(get_op_mode_str(it.old_opmode)),
628 LOG_STR_ARG(get_op_mode_str(it.new_opmode)));
629 break;
630 }
631 case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: {
632#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
633 auto it = event->event_info.distribute_sta_ip;
634 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
635 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
636 format_mac_addr_upper(it.mac, mac_buf);
637 ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", mac_buf, network::IPAddress(&it.ip).str_to(ip_buf),
638 it.aid);
639#endif
640 break;
641 }
642#endif
643 default:
644 break;
645 }
646
647 WiFiMockClass::_event_callback(event);
648}
649
651 uint8_t val = static_cast<uint8_t>(output_power * 4);
652 system_phy_set_max_tpw(val);
653 return true;
654}
656 if (!this->wifi_mode_(true, {}))
657 return false;
658
659 bool ret1, ret2;
660 ETS_UART_INTR_DISABLE();
661 ret1 = wifi_station_set_auto_connect(0);
662 ret2 = wifi_station_set_reconnect_policy(false);
663 ETS_UART_INTR_ENABLE();
664
665 if (!ret1 || !ret2) {
666 ESP_LOGV(TAG, "Disabling Auto-Connect failed");
667 }
668
669 delay(10);
670 return true;
671}
672
674 wifi_set_event_handler_cb(&WiFiComponent::wifi_event_callback);
675
676 // Make sure WiFi is in clean state before anything starts
677 this->wifi_mode_(false, false);
678}
679
681 station_status_t status = wifi_station_get_connect_status();
682 switch (status) {
683 case STATION_GOT_IP:
685 case STATION_NO_AP_FOUND:
687 ;
688 case STATION_CONNECT_FAIL:
689 case STATION_WRONG_PASSWORD:
691 case STATION_CONNECTING:
693 case STATION_IDLE:
694 default:
696 }
697}
699 static bool first_scan = false;
700
701 // enable STA
702 if (!this->wifi_mode_(true, {}))
703 return false;
704
705 // Reset scan_done_ before starting new scan to prevent stale flag from previous scan
706 // (e.g., roaming scan completed just before unexpected disconnect)
707 this->scan_done_ = false;
708
709 struct scan_config config {};
710 memset(&config, 0, sizeof(config));
711 config.ssid = nullptr;
712 config.bssid = nullptr;
713 config.channel = 0;
714 config.show_hidden = 1;
715#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
716 config.scan_type = passive ? WIFI_SCAN_TYPE_PASSIVE : WIFI_SCAN_TYPE_ACTIVE;
717 if (first_scan) {
718 if (passive) {
719 config.scan_time.passive = 200;
720 } else {
721 config.scan_time.active.min = 100;
722 config.scan_time.active.max = 200;
723 }
724 } else {
725 if (passive) {
726 config.scan_time.passive = 500;
727 } else {
728 config.scan_time.active.min = 400;
729 config.scan_time.active.max = 500;
730 }
731 }
732#endif
733 first_scan = false;
734 bool ret = wifi_station_scan(&config, &WiFiComponent::s_wifi_scan_done_callback);
735 if (!ret) {
736 ESP_LOGV(TAG, "wifi_station_scan failed");
737 return false;
738 }
739
740 return ret;
741}
743 bool ret = true;
744 // Only call disconnect if interface is up
745 if (wifi_get_opmode() & WIFI_STA)
746 ret = wifi_station_disconnect();
747 station_config conf{};
748 memset(&conf, 0, sizeof(conf));
749 ETS_UART_INTR_DISABLE();
750 wifi_station_set_config_current(&conf);
751 ETS_UART_INTR_ENABLE();
752 return ret;
753}
757
758void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
759 this->scan_result_.clear();
760
761 if (status != OK) {
762 ESP_LOGV(TAG, "Scan failed: %d", status);
763 // Don't call retry_connect() here - this callback runs in SDK system context
764 // where yield() cannot be called. Instead, just set scan_done_ and let
765 // check_scanning_finished() handle the empty scan_result_ from loop context.
766 this->scan_done_ = true;
767 return;
768 }
769
770 // Count the number of results first
771 auto *head = reinterpret_cast<bss_info *>(arg);
772 size_t count = 0;
773 for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) {
774 count++;
775 }
776
777 this->scan_result_.init(count);
778 for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) {
779 this->scan_result_.emplace_back(
780 bssid_t{it->bssid[0], it->bssid[1], it->bssid[2], it->bssid[3], it->bssid[4], it->bssid[5]},
781 std::string(reinterpret_cast<char *>(it->ssid), it->ssid_len), it->channel, it->rssi, it->authmode != AUTH_OPEN,
782 it->is_hidden != 0);
783 }
784 this->scan_done_ = true;
785#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
786 for (auto *listener : global_wifi_component->scan_results_listeners_) {
787 listener->on_wifi_scan_results(global_wifi_component->scan_result_);
788 }
789#endif
790}
791
792#ifdef USE_WIFI_AP
794 // enable AP
795 if (!this->wifi_mode_({}, true))
796 return false;
797
798 struct ip_info info {};
799 if (manual_ip.has_value()) {
800 info.ip = manual_ip->static_ip;
801 info.gw = manual_ip->gateway;
802 info.netmask = manual_ip->subnet;
803 } else {
804 info.ip = network::IPAddress(192, 168, 4, 1);
805 info.gw = network::IPAddress(192, 168, 4, 1);
806 info.netmask = network::IPAddress(255, 255, 255, 0);
807 }
808
809 if (wifi_softap_dhcps_status() == DHCP_STARTED) {
810 if (!wifi_softap_dhcps_stop()) {
811 ESP_LOGW(TAG, "Stopping DHCP server failed");
812 }
813 }
814
815 if (!wifi_set_ip_info(SOFTAP_IF, &info)) {
816 ESP_LOGE(TAG, "Set SoftAP info failed");
817 return false;
818 }
819
820#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0)
821 dhcpSoftAP.begin(&info);
822#endif
823
824 struct dhcps_lease lease {};
825 lease.enable = true;
826 network::IPAddress start_address = network::IPAddress(&info.ip);
827 start_address += 99;
828 lease.start_ip = start_address;
829#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
830 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
831#endif
832 ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str_to(ip_buf));
833 start_address += 10;
834 lease.end_ip = start_address;
835 ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str_to(ip_buf));
836 if (!wifi_softap_set_dhcps_lease(&lease)) {
837 ESP_LOGE(TAG, "Set SoftAP DHCP lease failed");
838 return false;
839 }
840
841 // lease time 1440 minutes (=24 hours)
842 if (!wifi_softap_set_dhcps_lease_time(1440)) {
843 ESP_LOGE(TAG, "Set SoftAP DHCP lease time failed");
844 return false;
845 }
846
847#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0)
848 ESP8266WiFiClass::softAPDhcpServer().setRouter(true); // send ROUTER option with netif's gateway IP
849#else
850 uint8_t mode = 1;
851 // bit0, 1 enables router information from ESP8266 SoftAP DHCP server.
852 if (!wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode)) {
853 ESP_LOGE(TAG, "wifi_softap_set_dhcps_offer_option failed");
854 return false;
855 }
856#endif
857
858 if (!wifi_softap_dhcps_start()) {
859 ESP_LOGE(TAG, "Starting SoftAP DHCPS failed");
860 return false;
861 }
862
863 return true;
864}
865
867 // enable AP
868 if (!this->wifi_mode_({}, true))
869 return false;
870
871 struct softap_config conf {};
872 if (ap.get_ssid().size() > sizeof(conf.ssid)) {
873 ESP_LOGE(TAG, "AP SSID too long");
874 return false;
875 }
876 memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
877 conf.ssid_len = static_cast<uint8>(ap.get_ssid().size());
878 conf.channel = ap.has_channel() ? ap.get_channel() : 1;
879 conf.ssid_hidden = ap.get_hidden();
880 conf.max_connection = 5;
881 conf.beacon_interval = 100;
882
883 if (ap.get_password().empty()) {
884 conf.authmode = AUTH_OPEN;
885 *conf.password = 0;
886 } else {
887 conf.authmode = AUTH_WPA2_PSK;
888 if (ap.get_password().size() > sizeof(conf.password)) {
889 ESP_LOGE(TAG, "AP password too long");
890 return false;
891 }
892 memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
893 }
894
895 ETS_UART_INTR_DISABLE();
896 bool ret = wifi_softap_set_config_current(&conf);
897 ETS_UART_INTR_ENABLE();
898
899 if (!ret) {
900 ESP_LOGV(TAG, "wifi_softap_set_config_current failed");
901 return false;
902 }
903
904#ifdef USE_WIFI_MANUAL_IP
905 if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
906 ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
907 return false;
908 }
909#else
910 if (!this->wifi_ap_ip_config_({})) {
911 ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
912 return false;
913 }
914#endif
915
916 return true;
917}
918
920 struct ip_info ip {};
921 wifi_get_ip_info(SOFTAP_IF, &ip);
922 return network::IPAddress(&ip.ip);
923}
924#endif // USE_WIFI_AP
925
927 bssid_t bssid{};
928 struct station_config conf {};
929 if (wifi_station_get_config(&conf)) {
930 std::copy_n(conf.bssid, bssid.size(), bssid.begin());
931 }
932 return bssid;
933}
934std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
935const char *WiFiComponent::wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer) {
936 struct station_config conf {};
937 if (!wifi_station_get_config(&conf)) {
938 buffer[0] = '\0';
939 return buffer.data();
940 }
941 // conf.ssid is uint8[32], not null-terminated if full
942 size_t len = strnlen(reinterpret_cast<const char *>(conf.ssid), sizeof(conf.ssid));
943 memcpy(buffer.data(), conf.ssid, len);
944 buffer[len] = '\0';
945 return buffer.data();
946}
948 if (WiFi.status() != WL_CONNECTED)
949 return WIFI_RSSI_DISCONNECTED;
950 int8_t rssi = WiFi.RSSI();
951 // Values >= 31 are error codes per NONOS SDK API, not valid RSSI readings
952 return rssi >= 31 ? WIFI_RSSI_DISCONNECTED : rssi;
953}
954int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
955network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
956network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
957network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; }
959
960} // namespace esphome::wifi
961#endif
962#endif
BedjetMode mode
BedJet operating mode.
uint8_t status
Definition bl0942.h:8
const std::string & get_name() const
Get the name of this Application set by pre_setup().
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
bool has_value() const
Definition optional.h:92
value_type value_or(U const &v) const
Definition optional.h:98
uint8_t get_channel() const
const std::string & get_ssid() const
const optional< EAPAuth > & get_eap() const
const std::string & get_password() const
const optional< ManualIP > & get_manual_ip() const
const bssid_t & get_bssid() const
void wifi_scan_done_callback_(void *arg, STATUS status)
const WiFiAP * get_selected_sta_() const
struct esphome::wifi::WiFiComponent::@175 pending_
wifi_scan_vector_t< WiFiScanResult > scan_result_
StaticVector< WiFiScanResultsListener *, ESPHOME_WIFI_SCAN_RESULTS_LISTENERS > scan_results_listeners_
bool wifi_sta_ip_config_(const optional< ManualIP > &manual_ip)
network::IPAddress get_dns_address(int num)
static void wifi_event_callback(System_Event_t *event)
StaticVector< WiFiConnectStateListener *, ESPHOME_WIFI_CONNECT_STATE_LISTENERS > connect_state_listeners_
const char * wifi_ssid_to(std::span< char, SSID_BUFFER_SIZE > buffer)
Write SSID to buffer without heap allocation.
static void s_wifi_scan_done_callback(void *arg, STATUS status)
network::IPAddress wifi_dns_ip_(int num)
StaticVector< WiFiIPStateListener *, ESPHOME_WIFI_IP_STATE_LISTENERS > ip_state_listeners_
bool wifi_ap_ip_config_(const optional< ManualIP > &manual_ip)
StaticVector< WiFiPowerSaveListener *, ESPHOME_WIFI_POWER_SAVE_LISTENERS > power_save_listeners_
bool wifi_apply_output_power_(float output_power)
WiFiSTAConnectStatus wifi_sta_connect_status_()
bool wifi_mode_(optional< bool > sta, optional< bool > ap)
network::IPAddresses wifi_sta_ip_addresses()
in_addr ip_addr_t
Definition ip_address.h:22
in_addr ip4_addr_t
Definition ip_address.h:23
mopeka_std_values val[4]
std::array< IPAddress, 5 > IPAddresses
Definition ip_address.h:180
const char *const TAG
Definition spi.cpp:7
std::array< uint8_t, 6 > bssid_t
const LogString * get_auth_mode_str(uint8_t mode)
const LogString * get_disconnect_reason_str(uint8_t reason)
struct netif * eagle_lwip_getif(int netif_index)
void netif_set_addr(struct netif *netif, const ip4_addr_t *ip, const ip4_addr_t *netmask, const ip4_addr_t *gw)
WiFiComponent * global_wifi_component
const LogString * get_op_mode_str(uint8_t mode)
std::string size_t len
Definition helpers.h:595
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
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)
Definition helpers.h:897
char * str_to(char *buf) const
Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
Definition ip_address.h:69