ESPHome 2025.12.1
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.get_bssid().has_value()) {
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.get_channel().has_value()) {
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(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid());
530 }
531#endif
532 break;
533 }
534 case EVENT_STAMODE_DISCONNECTED: {
535 auto it = event->event_info.disconnected;
536 char buf[33];
537 memcpy(buf, it.ssid, it.ssid_len);
538 buf[it.ssid_len] = '\0';
539 if (it.reason == REASON_NO_AP_FOUND) {
540 ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
541 s_sta_connect_not_found = true;
542 } else {
543 char bssid_s[18];
544 format_mac_addr_upper(it.bssid, bssid_s);
545 ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
546 LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
547 s_sta_connect_error = true;
548 }
549 s_sta_connected = false;
550 s_sta_connecting = false;
551#ifdef USE_WIFI_LISTENERS
552 for (auto *listener : global_wifi_component->connect_state_listeners_) {
553 listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0}));
554 }
555#endif
556 break;
557 }
558 case EVENT_STAMODE_AUTHMODE_CHANGE: {
559 auto it = event->event_info.auth_change;
560 ESP_LOGV(TAG, "Changed Authmode old=%s new=%s", LOG_STR_ARG(get_auth_mode_str(it.old_mode)),
561 LOG_STR_ARG(get_auth_mode_str(it.new_mode)));
562 // Mitigate CVE-2020-12638
563 // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors
564 if (it.old_mode != AUTH_OPEN && it.new_mode == AUTH_OPEN) {
565 ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting");
566 // we can't call retry_connect() from this context, so disconnect immediately
567 // and notify main thread with error_from_callback_
568 wifi_station_disconnect();
570 }
571 break;
572 }
573 case EVENT_STAMODE_GOT_IP: {
574 auto it = event->event_info.got_ip;
575 ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(),
576 format_ip_addr(it.mask).c_str());
577 s_sta_got_ip = true;
578#ifdef USE_WIFI_LISTENERS
579 for (auto *listener : global_wifi_component->ip_state_listeners_) {
582 }
583#endif
584 break;
585 }
586 case EVENT_STAMODE_DHCP_TIMEOUT: {
587 ESP_LOGW(TAG, "DHCP request timeout");
588 break;
589 }
590 case EVENT_SOFTAPMODE_STACONNECTED: {
591 auto it = event->event_info.sta_connected;
592 ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
593 break;
594 }
595 case EVENT_SOFTAPMODE_STADISCONNECTED: {
596 auto it = event->event_info.sta_disconnected;
597 ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
598 break;
599 }
600 case EVENT_SOFTAPMODE_PROBEREQRECVED: {
601 auto it = event->event_info.ap_probereqrecved;
602 ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
603 break;
604 }
605#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
606 case EVENT_OPMODE_CHANGED: {
607 auto it = event->event_info.opmode_changed;
608 ESP_LOGV(TAG, "Changed Mode old=%s new=%s", LOG_STR_ARG(get_op_mode_str(it.old_opmode)),
609 LOG_STR_ARG(get_op_mode_str(it.new_opmode)));
610 break;
611 }
612 case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: {
613 auto it = event->event_info.distribute_sta_ip;
614 ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_address_pretty(it.mac).c_str(),
615 format_ip_addr(it.ip).c_str(), it.aid);
616 break;
617 }
618#endif
619 default:
620 break;
621 }
622
623 if (event->event == EVENT_STAMODE_DISCONNECTED) {
625 }
626
627 WiFiMockClass::_event_callback(event);
628}
629
631 uint8_t val = static_cast<uint8_t>(output_power * 4);
632 system_phy_set_max_tpw(val);
633 return true;
634}
636 if (!this->wifi_mode_(true, {}))
637 return false;
638
639 bool ret1, ret2;
640 ETS_UART_INTR_DISABLE();
641 ret1 = wifi_station_set_auto_connect(0);
642 ret2 = wifi_station_set_reconnect_policy(false);
643 ETS_UART_INTR_ENABLE();
644
645 if (!ret1 || !ret2) {
646 ESP_LOGV(TAG, "Disabling Auto-Connect failed");
647 }
648
649 delay(10);
650 return true;
651}
652
654 wifi_set_event_handler_cb(&WiFiComponent::wifi_event_callback);
655
656 // Make sure WiFi is in clean state before anything starts
657 this->wifi_mode_(false, false);
658}
659
661 station_status_t status = wifi_station_get_connect_status();
662 switch (status) {
663 case STATION_GOT_IP:
665 case STATION_NO_AP_FOUND:
667 ;
668 case STATION_CONNECT_FAIL:
669 case STATION_WRONG_PASSWORD:
671 case STATION_CONNECTING:
673 case STATION_IDLE:
674 default:
676 }
677}
679 static bool first_scan = false;
680
681 // enable STA
682 if (!this->wifi_mode_(true, {}))
683 return false;
684
685 struct scan_config config {};
686 memset(&config, 0, sizeof(config));
687 config.ssid = nullptr;
688 config.bssid = nullptr;
689 config.channel = 0;
690 config.show_hidden = 1;
691#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
692 config.scan_type = passive ? WIFI_SCAN_TYPE_PASSIVE : WIFI_SCAN_TYPE_ACTIVE;
693 if (first_scan) {
694 if (passive) {
695 config.scan_time.passive = 200;
696 } else {
697 config.scan_time.active.min = 100;
698 config.scan_time.active.max = 200;
699 }
700 } else {
701 if (passive) {
702 config.scan_time.passive = 500;
703 } else {
704 config.scan_time.active.min = 400;
705 config.scan_time.active.max = 500;
706 }
707 }
708#endif
709 first_scan = false;
710 bool ret = wifi_station_scan(&config, &WiFiComponent::s_wifi_scan_done_callback);
711 if (!ret) {
712 ESP_LOGV(TAG, "wifi_station_scan failed");
713 return false;
714 }
715
716 return ret;
717}
719 bool ret = true;
720 // Only call disconnect if interface is up
721 if (wifi_get_opmode() & WIFI_STA)
722 ret = wifi_station_disconnect();
723 station_config conf{};
724 memset(&conf, 0, sizeof(conf));
725 ETS_UART_INTR_DISABLE();
726 wifi_station_set_config_current(&conf);
727 ETS_UART_INTR_ENABLE();
728 return ret;
729}
733
734void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
735 this->scan_result_.clear();
736
737 if (status != OK) {
738 ESP_LOGV(TAG, "Scan failed: %d", status);
739 this->retry_connect();
740 return;
741 }
742
743 // Count the number of results first
744 auto *head = reinterpret_cast<bss_info *>(arg);
745 size_t count = 0;
746 for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) {
747 count++;
748 }
749
750 this->scan_result_.init(count);
751 for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) {
752 this->scan_result_.emplace_back(
753 bssid_t{it->bssid[0], it->bssid[1], it->bssid[2], it->bssid[3], it->bssid[4], it->bssid[5]},
754 std::string(reinterpret_cast<char *>(it->ssid), it->ssid_len), it->channel, it->rssi, it->authmode != AUTH_OPEN,
755 it->is_hidden != 0);
756 }
757 this->scan_done_ = true;
758#ifdef USE_WIFI_LISTENERS
759 for (auto *listener : global_wifi_component->scan_results_listeners_) {
760 listener->on_wifi_scan_results(global_wifi_component->scan_result_);
761 }
762#endif
763}
764
765#ifdef USE_WIFI_AP
767 // enable AP
768 if (!this->wifi_mode_({}, true))
769 return false;
770
771 struct ip_info info {};
772 if (manual_ip.has_value()) {
773 info.ip = manual_ip->static_ip;
774 info.gw = manual_ip->gateway;
775 info.netmask = manual_ip->subnet;
776 } else {
777 info.ip = network::IPAddress(192, 168, 4, 1);
778 info.gw = network::IPAddress(192, 168, 4, 1);
779 info.netmask = network::IPAddress(255, 255, 255, 0);
780 }
781
782 if (wifi_softap_dhcps_status() == DHCP_STARTED) {
783 if (!wifi_softap_dhcps_stop()) {
784 ESP_LOGW(TAG, "Stopping DHCP server failed");
785 }
786 }
787
788 if (!wifi_set_ip_info(SOFTAP_IF, &info)) {
789 ESP_LOGE(TAG, "Set SoftAP info failed");
790 return false;
791 }
792
793#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0)
794 dhcpSoftAP.begin(&info);
795#endif
796
797 struct dhcps_lease lease {};
798 lease.enable = true;
799 network::IPAddress start_address = network::IPAddress(&info.ip);
800 start_address += 99;
801 lease.start_ip = start_address;
802 ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
803 start_address += 10;
804 lease.end_ip = start_address;
805 ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
806 if (!wifi_softap_set_dhcps_lease(&lease)) {
807 ESP_LOGE(TAG, "Set SoftAP DHCP lease failed");
808 return false;
809 }
810
811 // lease time 1440 minutes (=24 hours)
812 if (!wifi_softap_set_dhcps_lease_time(1440)) {
813 ESP_LOGE(TAG, "Set SoftAP DHCP lease time failed");
814 return false;
815 }
816
817#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0)
818 ESP8266WiFiClass::softAPDhcpServer().setRouter(true); // send ROUTER option with netif's gateway IP
819#else
820 uint8_t mode = 1;
821 // bit0, 1 enables router information from ESP8266 SoftAP DHCP server.
822 if (!wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode)) {
823 ESP_LOGE(TAG, "wifi_softap_set_dhcps_offer_option failed");
824 return false;
825 }
826#endif
827
828 if (!wifi_softap_dhcps_start()) {
829 ESP_LOGE(TAG, "Starting SoftAP DHCPS failed");
830 return false;
831 }
832
833 return true;
834}
835
837 // enable AP
838 if (!this->wifi_mode_({}, true))
839 return false;
840
841 struct softap_config conf {};
842 if (ap.get_ssid().size() > sizeof(conf.ssid)) {
843 ESP_LOGE(TAG, "AP SSID too long");
844 return false;
845 }
846 memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
847 conf.ssid_len = static_cast<uint8>(ap.get_ssid().size());
848 conf.channel = ap.get_channel().value_or(1);
849 conf.ssid_hidden = ap.get_hidden();
850 conf.max_connection = 5;
851 conf.beacon_interval = 100;
852
853 if (ap.get_password().empty()) {
854 conf.authmode = AUTH_OPEN;
855 *conf.password = 0;
856 } else {
857 conf.authmode = AUTH_WPA2_PSK;
858 if (ap.get_password().size() > sizeof(conf.password)) {
859 ESP_LOGE(TAG, "AP password too long");
860 return false;
861 }
862 memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
863 }
864
865 ETS_UART_INTR_DISABLE();
866 bool ret = wifi_softap_set_config_current(&conf);
867 ETS_UART_INTR_ENABLE();
868
869 if (!ret) {
870 ESP_LOGV(TAG, "wifi_softap_set_config_current failed");
871 return false;
872 }
873
874#ifdef USE_WIFI_MANUAL_IP
875 if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
876 ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
877 return false;
878 }
879#else
880 if (!this->wifi_ap_ip_config_({})) {
881 ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
882 return false;
883 }
884#endif
885
886 return true;
887}
888
890 struct ip_info ip {};
891 wifi_get_ip_info(SOFTAP_IF, &ip);
892 return network::IPAddress(&ip.ip);
893}
894#endif // USE_WIFI_AP
895
897 bssid_t bssid{};
898 struct station_config conf {};
899 if (wifi_station_get_config(&conf)) {
900 std::copy_n(conf.bssid, bssid.size(), bssid.begin());
901 }
902 return bssid;
903}
904std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
906 if (WiFi.status() != WL_CONNECTED)
907 return WIFI_RSSI_DISCONNECTED;
908 int8_t rssi = WiFi.RSSI();
909 // Values >= 31 are error codes per NONOS SDK API, not valid RSSI readings
910 return rssi >= 31 ? WIFI_RSSI_DISCONNECTED : rssi;
911}
912int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
913network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
914network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
915network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; }
917
918} // namespace esphome::wifi
919#endif
920#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().
bool has_value() const
Definition optional.h:92
value_type value_or(U const &v) const
Definition optional.h:98
const optional< bssid_t > & get_bssid() const
const std::string & get_ssid() const
const optional< uint8_t > & get_channel() const
const optional< EAPAuth > & get_eap() const
const std::string & get_password() const
const optional< ManualIP > & get_manual_ip() const
void wifi_scan_done_callback_(void *arg, STATUS status)
std::vector< WiFiIPStateListener * > ip_state_listeners_
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)
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:149
const char *const TAG
Definition spi.cpp:8
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)
Definition helpers.h:635
std::string format_mac_address_pretty(const uint8_t *mac)
Definition helpers.cpp:286
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:31
Application App
Global storage of Application pointer - only one Application can exist.
std::string str() const
Definition ip_address.h:52