ESPHome 2026.1.0-dev
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_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 ESP_LOGV(TAG, "Address %s", addr.toString().c_str());
375 if (addr.isV6()) {
376 ipv6_addr_count++;
377 }
378 }
379 delay(500); // NOLINT
380 connected = (ipv6_addr_count >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
381 }
382#endif /* USE_NETWORK_IPV6 */
383
384 if (ap.has_channel()) {
385 ret = wifi_set_channel(ap.get_channel());
386 if (!ret) {
387 ESP_LOGV(TAG, "wifi_set_channel failed");
388 return false;
389 }
390 }
391
392 return true;
393}
394
395class WiFiMockClass : public ESP8266WiFiGenericClass {
396 public:
397 static void _event_callback(void *event) { ESP8266WiFiGenericClass::_eventCallback(event); } // NOLINT
398};
399
400const LogString *get_auth_mode_str(uint8_t mode) {
401 switch (mode) {
402 case AUTH_OPEN:
403 return LOG_STR("OPEN");
404 case AUTH_WEP:
405 return LOG_STR("WEP");
406 case AUTH_WPA_PSK:
407 return LOG_STR("WPA PSK");
408 case AUTH_WPA2_PSK:
409 return LOG_STR("WPA2 PSK");
410 case AUTH_WPA_WPA2_PSK:
411 return LOG_STR("WPA/WPA2 PSK");
412 default:
413 return LOG_STR("UNKNOWN");
414 }
415}
416#ifdef ipv4_addr
417std::string format_ip_addr(struct ipv4_addr ip) {
418 char buf[20];
419 sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16),
420 uint8_t(ip.addr >> 24));
421 return buf;
422}
423#else
424std::string format_ip_addr(struct ip_addr ip) {
425 char buf[20];
426 sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16),
427 uint8_t(ip.addr >> 24));
428 return buf;
429}
430#endif
431const LogString *get_op_mode_str(uint8_t mode) {
432 switch (mode) {
433 case WIFI_OFF:
434 return LOG_STR("OFF");
435 case WIFI_STA:
436 return LOG_STR("STA");
437 case WIFI_AP:
438 return LOG_STR("AP");
439 case WIFI_AP_STA:
440 return LOG_STR("AP+STA");
441 default:
442 return LOG_STR("UNKNOWN");
443 }
444}
445
446const LogString *get_disconnect_reason_str(uint8_t reason) {
447 /* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the
448 * REASON_* constants aren't continuous, and GCC will fill in the gap with the default value -- wasting 4 bytes of RAM
449 * per entry. As there's ~175 default entries, this wastes 700 bytes of RAM.
450 */
451 if (reason <= REASON_CIPHER_SUITE_REJECTED) { // This must be the last constant with a value <200
452 switch (reason) {
453 case REASON_AUTH_EXPIRE:
454 return LOG_STR("Auth Expired");
455 case REASON_AUTH_LEAVE:
456 return LOG_STR("Auth Leave");
457 case REASON_ASSOC_EXPIRE:
458 return LOG_STR("Association Expired");
459 case REASON_ASSOC_TOOMANY:
460 return LOG_STR("Too Many Associations");
461 case REASON_NOT_AUTHED:
462 return LOG_STR("Not Authenticated");
463 case REASON_NOT_ASSOCED:
464 return LOG_STR("Not Associated");
465 case REASON_ASSOC_LEAVE:
466 return LOG_STR("Association Leave");
467 case REASON_ASSOC_NOT_AUTHED:
468 return LOG_STR("Association not Authenticated");
469 case REASON_DISASSOC_PWRCAP_BAD:
470 return LOG_STR("Disassociate Power Cap Bad");
471 case REASON_DISASSOC_SUPCHAN_BAD:
472 return LOG_STR("Disassociate Supported Channel Bad");
473 case REASON_IE_INVALID:
474 return LOG_STR("IE Invalid");
475 case REASON_MIC_FAILURE:
476 return LOG_STR("Mic Failure");
477 case REASON_4WAY_HANDSHAKE_TIMEOUT:
478 return LOG_STR("4-Way Handshake Timeout");
479 case REASON_GROUP_KEY_UPDATE_TIMEOUT:
480 return LOG_STR("Group Key Update Timeout");
481 case REASON_IE_IN_4WAY_DIFFERS:
482 return LOG_STR("IE In 4-Way Handshake Differs");
483 case REASON_GROUP_CIPHER_INVALID:
484 return LOG_STR("Group Cipher Invalid");
485 case REASON_PAIRWISE_CIPHER_INVALID:
486 return LOG_STR("Pairwise Cipher Invalid");
487 case REASON_AKMP_INVALID:
488 return LOG_STR("AKMP Invalid");
489 case REASON_UNSUPP_RSN_IE_VERSION:
490 return LOG_STR("Unsupported RSN IE version");
491 case REASON_INVALID_RSN_IE_CAP:
492 return LOG_STR("Invalid RSN IE Cap");
493 case REASON_802_1X_AUTH_FAILED:
494 return LOG_STR("802.1x Authentication Failed");
495 case REASON_CIPHER_SUITE_REJECTED:
496 return LOG_STR("Cipher Suite Rejected");
497 }
498 }
499
500 switch (reason) {
501 case REASON_BEACON_TIMEOUT:
502 return LOG_STR("Beacon Timeout");
503 case REASON_NO_AP_FOUND:
504 return LOG_STR("AP Not Found");
505 case REASON_AUTH_FAIL:
506 return LOG_STR("Authentication Failed");
507 case REASON_ASSOC_FAIL:
508 return LOG_STR("Association Failed");
509 case REASON_HANDSHAKE_TIMEOUT:
510 return LOG_STR("Handshake Failed");
511 case REASON_UNSPECIFIED:
512 default:
513 return LOG_STR("Unspecified");
514 }
515}
516
517void WiFiComponent::wifi_event_callback(System_Event_t *event) {
518 switch (event->event) {
519 case EVENT_STAMODE_CONNECTED: {
520 auto it = event->event_info.connected;
521 char buf[33];
522 memcpy(buf, it.ssid, it.ssid_len);
523 buf[it.ssid_len] = '\0';
524 ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
525 it.channel);
526 s_sta_connected = true;
527#ifdef USE_WIFI_LISTENERS
528 for (auto *listener : global_wifi_component->connect_state_listeners_) {
529 listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid);
530 }
531 // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
532#ifdef USE_WIFI_MANUAL_IP
533 if (const WiFiAP *config = global_wifi_component->get_selected_sta_();
534 config && config->get_manual_ip().has_value()) {
535 for (auto *listener : global_wifi_component->ip_state_listeners_) {
536 listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(),
538 }
539 }
540#endif
541#endif
542 break;
543 }
544 case EVENT_STAMODE_DISCONNECTED: {
545 auto it = event->event_info.disconnected;
546 char buf[33];
547 memcpy(buf, it.ssid, it.ssid_len);
548 buf[it.ssid_len] = '\0';
549 if (it.reason == REASON_NO_AP_FOUND) {
550 ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
551 s_sta_connect_not_found = true;
552 } else {
553 char bssid_s[18];
554 format_mac_addr_upper(it.bssid, bssid_s);
555 ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
556 LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
557 s_sta_connect_error = true;
558 }
559 s_sta_connected = false;
560 s_sta_connecting = false;
561#ifdef USE_WIFI_LISTENERS
562 static constexpr uint8_t EMPTY_BSSID[6] = {};
563 for (auto *listener : global_wifi_component->connect_state_listeners_) {
564 listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID);
565 }
566#endif
567 break;
568 }
569 case EVENT_STAMODE_AUTHMODE_CHANGE: {
570 auto it = event->event_info.auth_change;
571 ESP_LOGV(TAG, "Changed Authmode old=%s new=%s", LOG_STR_ARG(get_auth_mode_str(it.old_mode)),
572 LOG_STR_ARG(get_auth_mode_str(it.new_mode)));
573 // Mitigate CVE-2020-12638
574 // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors
575 if (it.old_mode != AUTH_OPEN && it.new_mode == AUTH_OPEN) {
576 ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting");
577 // we can't call retry_connect() from this context, so disconnect immediately
578 // and notify main thread with error_from_callback_
579 wifi_station_disconnect();
581 }
582 break;
583 }
584 case EVENT_STAMODE_GOT_IP: {
585 auto it = event->event_info.got_ip;
586 ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(),
587 format_ip_addr(it.mask).c_str());
588 s_sta_got_ip = true;
589#ifdef USE_WIFI_LISTENERS
590 for (auto *listener : global_wifi_component->ip_state_listeners_) {
593 }
594#endif
595 break;
596 }
597 case EVENT_STAMODE_DHCP_TIMEOUT: {
598 ESP_LOGW(TAG, "DHCP request timeout");
599 break;
600 }
601 case EVENT_SOFTAPMODE_STACONNECTED: {
602 auto it = event->event_info.sta_connected;
603 ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
604 break;
605 }
606 case EVENT_SOFTAPMODE_STADISCONNECTED: {
607 auto it = event->event_info.sta_disconnected;
608 ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
609 break;
610 }
611 case EVENT_SOFTAPMODE_PROBEREQRECVED: {
612 auto it = event->event_info.ap_probereqrecved;
613 ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
614 break;
615 }
616#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
617 case EVENT_OPMODE_CHANGED: {
618 auto it = event->event_info.opmode_changed;
619 ESP_LOGV(TAG, "Changed Mode old=%s new=%s", LOG_STR_ARG(get_op_mode_str(it.old_opmode)),
620 LOG_STR_ARG(get_op_mode_str(it.new_opmode)));
621 break;
622 }
623 case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: {
624 auto it = event->event_info.distribute_sta_ip;
625 ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_address_pretty(it.mac).c_str(),
626 format_ip_addr(it.ip).c_str(), it.aid);
627 break;
628 }
629#endif
630 default:
631 break;
632 }
633
634 if (event->event == EVENT_STAMODE_DISCONNECTED) {
636 }
637
638 WiFiMockClass::_event_callback(event);
639}
640
642 uint8_t val = static_cast<uint8_t>(output_power * 4);
643 system_phy_set_max_tpw(val);
644 return true;
645}
647 if (!this->wifi_mode_(true, {}))
648 return false;
649
650 bool ret1, ret2;
651 ETS_UART_INTR_DISABLE();
652 ret1 = wifi_station_set_auto_connect(0);
653 ret2 = wifi_station_set_reconnect_policy(false);
654 ETS_UART_INTR_ENABLE();
655
656 if (!ret1 || !ret2) {
657 ESP_LOGV(TAG, "Disabling Auto-Connect failed");
658 }
659
660 delay(10);
661 return true;
662}
663
665 wifi_set_event_handler_cb(&WiFiComponent::wifi_event_callback);
666
667 // Make sure WiFi is in clean state before anything starts
668 this->wifi_mode_(false, false);
669}
670
672 station_status_t status = wifi_station_get_connect_status();
673 switch (status) {
674 case STATION_GOT_IP:
676 case STATION_NO_AP_FOUND:
678 ;
679 case STATION_CONNECT_FAIL:
680 case STATION_WRONG_PASSWORD:
682 case STATION_CONNECTING:
684 case STATION_IDLE:
685 default:
687 }
688}
690 static bool first_scan = false;
691
692 // enable STA
693 if (!this->wifi_mode_(true, {}))
694 return false;
695
696 struct scan_config config {};
697 memset(&config, 0, sizeof(config));
698 config.ssid = nullptr;
699 config.bssid = nullptr;
700 config.channel = 0;
701 config.show_hidden = 1;
702#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
703 config.scan_type = passive ? WIFI_SCAN_TYPE_PASSIVE : WIFI_SCAN_TYPE_ACTIVE;
704 if (first_scan) {
705 if (passive) {
706 config.scan_time.passive = 200;
707 } else {
708 config.scan_time.active.min = 100;
709 config.scan_time.active.max = 200;
710 }
711 } else {
712 if (passive) {
713 config.scan_time.passive = 500;
714 } else {
715 config.scan_time.active.min = 400;
716 config.scan_time.active.max = 500;
717 }
718 }
719#endif
720 first_scan = false;
721 bool ret = wifi_station_scan(&config, &WiFiComponent::s_wifi_scan_done_callback);
722 if (!ret) {
723 ESP_LOGV(TAG, "wifi_station_scan failed");
724 return false;
725 }
726
727 return ret;
728}
730 bool ret = true;
731 // Only call disconnect if interface is up
732 if (wifi_get_opmode() & WIFI_STA)
733 ret = wifi_station_disconnect();
734 station_config conf{};
735 memset(&conf, 0, sizeof(conf));
736 ETS_UART_INTR_DISABLE();
737 wifi_station_set_config_current(&conf);
738 ETS_UART_INTR_ENABLE();
739 return ret;
740}
744
745void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
746 this->scan_result_.clear();
747
748 if (status != OK) {
749 ESP_LOGV(TAG, "Scan failed: %d", status);
750 this->retry_connect();
751 return;
752 }
753
754 // Count the number of results first
755 auto *head = reinterpret_cast<bss_info *>(arg);
756 size_t count = 0;
757 for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) {
758 count++;
759 }
760
761 this->scan_result_.init(count);
762 for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) {
763 this->scan_result_.emplace_back(
764 bssid_t{it->bssid[0], it->bssid[1], it->bssid[2], it->bssid[3], it->bssid[4], it->bssid[5]},
765 std::string(reinterpret_cast<char *>(it->ssid), it->ssid_len), it->channel, it->rssi, it->authmode != AUTH_OPEN,
766 it->is_hidden != 0);
767 }
768 this->scan_done_ = true;
769#ifdef USE_WIFI_LISTENERS
770 for (auto *listener : global_wifi_component->scan_results_listeners_) {
771 listener->on_wifi_scan_results(global_wifi_component->scan_result_);
772 }
773#endif
774}
775
776#ifdef USE_WIFI_AP
778 // enable AP
779 if (!this->wifi_mode_({}, true))
780 return false;
781
782 struct ip_info info {};
783 if (manual_ip.has_value()) {
784 info.ip = manual_ip->static_ip;
785 info.gw = manual_ip->gateway;
786 info.netmask = manual_ip->subnet;
787 } else {
788 info.ip = network::IPAddress(192, 168, 4, 1);
789 info.gw = network::IPAddress(192, 168, 4, 1);
790 info.netmask = network::IPAddress(255, 255, 255, 0);
791 }
792
793 if (wifi_softap_dhcps_status() == DHCP_STARTED) {
794 if (!wifi_softap_dhcps_stop()) {
795 ESP_LOGW(TAG, "Stopping DHCP server failed");
796 }
797 }
798
799 if (!wifi_set_ip_info(SOFTAP_IF, &info)) {
800 ESP_LOGE(TAG, "Set SoftAP info failed");
801 return false;
802 }
803
804#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0)
805 dhcpSoftAP.begin(&info);
806#endif
807
808 struct dhcps_lease lease {};
809 lease.enable = true;
810 network::IPAddress start_address = network::IPAddress(&info.ip);
811 start_address += 99;
812 lease.start_ip = start_address;
813 ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
814 start_address += 10;
815 lease.end_ip = start_address;
816 ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
817 if (!wifi_softap_set_dhcps_lease(&lease)) {
818 ESP_LOGE(TAG, "Set SoftAP DHCP lease failed");
819 return false;
820 }
821
822 // lease time 1440 minutes (=24 hours)
823 if (!wifi_softap_set_dhcps_lease_time(1440)) {
824 ESP_LOGE(TAG, "Set SoftAP DHCP lease time failed");
825 return false;
826 }
827
828#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0)
829 ESP8266WiFiClass::softAPDhcpServer().setRouter(true); // send ROUTER option with netif's gateway IP
830#else
831 uint8_t mode = 1;
832 // bit0, 1 enables router information from ESP8266 SoftAP DHCP server.
833 if (!wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode)) {
834 ESP_LOGE(TAG, "wifi_softap_set_dhcps_offer_option failed");
835 return false;
836 }
837#endif
838
839 if (!wifi_softap_dhcps_start()) {
840 ESP_LOGE(TAG, "Starting SoftAP DHCPS failed");
841 return false;
842 }
843
844 return true;
845}
846
848 // enable AP
849 if (!this->wifi_mode_({}, true))
850 return false;
851
852 struct softap_config conf {};
853 if (ap.get_ssid().size() > sizeof(conf.ssid)) {
854 ESP_LOGE(TAG, "AP SSID too long");
855 return false;
856 }
857 memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
858 conf.ssid_len = static_cast<uint8>(ap.get_ssid().size());
859 conf.channel = ap.has_channel() ? ap.get_channel() : 1;
860 conf.ssid_hidden = ap.get_hidden();
861 conf.max_connection = 5;
862 conf.beacon_interval = 100;
863
864 if (ap.get_password().empty()) {
865 conf.authmode = AUTH_OPEN;
866 *conf.password = 0;
867 } else {
868 conf.authmode = AUTH_WPA2_PSK;
869 if (ap.get_password().size() > sizeof(conf.password)) {
870 ESP_LOGE(TAG, "AP password too long");
871 return false;
872 }
873 memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
874 }
875
876 ETS_UART_INTR_DISABLE();
877 bool ret = wifi_softap_set_config_current(&conf);
878 ETS_UART_INTR_ENABLE();
879
880 if (!ret) {
881 ESP_LOGV(TAG, "wifi_softap_set_config_current failed");
882 return false;
883 }
884
885#ifdef USE_WIFI_MANUAL_IP
886 if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
887 ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
888 return false;
889 }
890#else
891 if (!this->wifi_ap_ip_config_({})) {
892 ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
893 return false;
894 }
895#endif
896
897 return true;
898}
899
901 struct ip_info ip {};
902 wifi_get_ip_info(SOFTAP_IF, &ip);
903 return network::IPAddress(&ip.ip);
904}
905#endif // USE_WIFI_AP
906
908 bssid_t bssid{};
909 struct station_config conf {};
910 if (wifi_station_get_config(&conf)) {
911 std::copy_n(conf.bssid, bssid.size(), bssid.begin());
912 }
913 return bssid;
914}
915std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
916const char *WiFiComponent::wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer) {
917 struct station_config conf {};
918 if (!wifi_station_get_config(&conf)) {
919 buffer[0] = '\0';
920 return buffer.data();
921 }
922 // conf.ssid is uint8[32], not null-terminated if full
923 size_t len = strnlen(reinterpret_cast<const char *>(conf.ssid), sizeof(conf.ssid));
924 memcpy(buffer.data(), conf.ssid, len);
925 buffer[len] = '\0';
926 return buffer.data();
927}
929 if (WiFi.status() != WL_CONNECTED)
930 return WIFI_RSSI_DISCONNECTED;
931 int8_t rssi = WiFi.RSSI();
932 // Values >= 31 are error codes per NONOS SDK API, not valid RSSI readings
933 return rssi >= 31 ? WIFI_RSSI_DISCONNECTED : rssi;
934}
935int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
936network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
937network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
938network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; }
940
941} // namespace esphome::wifi
942#endif
943#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:22
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)
std::vector< WiFiIPStateListener * > ip_state_listeners_
const WiFiAP * get_selected_sta_() const
std::vector< WiFiScanResultsListener * > scan_results_listeners_
wifi_scan_vector_t< WiFiScanResult > scan_result_
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)
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)
bool wifi_ap_ip_config_(const optional< ManualIP > &manual_ip)
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()
std::vector< WiFiConnectStateListener * > connect_state_listeners_
std::vector< WiFiPowerSaveListener * > power_save_listeners_
uint32_t ip_addr
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:158
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)
std::string format_ip_addr(struct ipv4_addr ip)
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)
void 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:765
std::string size_t len
Definition helpers.h:533
std::string format_mac_address_pretty(const uint8_t *mac)
Definition helpers.cpp:283
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
Application App
Global storage of Application pointer - only one Application can exist.
std::string str() const
Definition ip_address.h:55