ESPHome 2025.9.0-dev
Loading...
Searching...
No Matches
wifi_component_esp32_arduino.cpp
Go to the documentation of this file.
1#include "wifi_component.h"
2
3#ifdef USE_WIFI
4#ifdef USE_ESP32_FRAMEWORK_ARDUINO
5
6#include <esp_netif.h>
7#include <esp_wifi.h>
8
9#include <algorithm>
10#include <utility>
11#ifdef USE_WIFI_WPA2_EAP
12#include <esp_eap_client.h>
13#endif
14
15#ifdef USE_WIFI_AP
16#include "dhcpserver/dhcpserver.h"
17#endif // USE_WIFI_AP
18
19#include "lwip/apps/sntp.h"
20#include "lwip/dns.h"
21#include "lwip/err.h"
22
24#include "esphome/core/hal.h"
26#include "esphome/core/log.h"
27#include "esphome/core/util.h"
28
29namespace esphome {
30namespace wifi {
31
32static const char *const TAG = "wifi_esp32";
33
34static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
35#ifdef USE_WIFI_AP
36static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
37#endif // USE_WIFI_AP
38
39static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
40
42 uint8_t mac[6];
45 set_mac_address(mac);
46 }
47 auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2);
48 WiFi.onEvent(f);
49 WiFi.persistent(false);
50 // Make sure WiFi is in clean state before anything starts
51 this->wifi_mode_(false, false);
52}
53
55 wifi_mode_t current_mode = WiFiClass::getMode();
56 bool current_sta = current_mode == WIFI_MODE_STA || current_mode == WIFI_MODE_APSTA;
57 bool current_ap = current_mode == WIFI_MODE_AP || current_mode == WIFI_MODE_APSTA;
58
59 bool set_sta = sta.value_or(current_sta);
60 bool set_ap = ap.value_or(current_ap);
61
62 wifi_mode_t set_mode;
63 if (set_sta && set_ap) {
64 set_mode = WIFI_MODE_APSTA;
65 } else if (set_sta && !set_ap) {
66 set_mode = WIFI_MODE_STA;
67 } else if (!set_sta && set_ap) {
68 set_mode = WIFI_MODE_AP;
69 } else {
70 set_mode = WIFI_MODE_NULL;
71 }
72
73 if (current_mode == set_mode)
74 return true;
75
76 if (set_sta && !current_sta) {
77 ESP_LOGV(TAG, "Enabling STA");
78 } else if (!set_sta && current_sta) {
79 ESP_LOGV(TAG, "Disabling STA");
80 }
81 if (set_ap && !current_ap) {
82 ESP_LOGV(TAG, "Enabling AP");
83 } else if (!set_ap && current_ap) {
84 ESP_LOGV(TAG, "Disabling AP");
85 }
86
87 bool ret = WiFiClass::mode(set_mode);
88
89 if (!ret) {
90 ESP_LOGW(TAG, "Setting mode failed");
91 return false;
92 }
93
94 // WiFiClass::mode above calls esp_netif_create_default_wifi_sta() and
95 // esp_netif_create_default_wifi_ap(), which creates the interfaces.
96 // s_sta_netif handle is set during ESPHOME_EVENT_ID_WIFI_STA_START event
97
98#ifdef USE_WIFI_AP
99 if (set_ap)
100 s_ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
101#endif
102
103 return ret;
104}
105
107 if (!this->wifi_mode_(true, {}))
108 return false;
109
110 WiFi.setAutoReconnect(false);
111 delay(10);
112 return true;
113}
114
116 int8_t val = static_cast<int8_t>(output_power * 4);
117 return esp_wifi_set_max_tx_power(val) == ESP_OK;
118}
119
121 wifi_ps_type_t power_save;
122 switch (this->power_save_) {
124 power_save = WIFI_PS_MIN_MODEM;
125 break;
127 power_save = WIFI_PS_MAX_MODEM;
128 break;
130 default:
131 power_save = WIFI_PS_NONE;
132 break;
133 }
134 return esp_wifi_set_ps(power_save) == ESP_OK;
135}
136
138 // enable STA
139 if (!this->wifi_mode_(true, {}))
140 return false;
141
142 // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t
143 wifi_config_t conf;
144 memset(&conf, 0, sizeof(conf));
145 if (ap.get_ssid().size() > sizeof(conf.sta.ssid)) {
146 ESP_LOGE(TAG, "SSID too long");
147 return false;
148 }
149 if (ap.get_password().size() > sizeof(conf.sta.password)) {
150 ESP_LOGE(TAG, "Password too long");
151 return false;
152 }
153 memcpy(reinterpret_cast<char *>(conf.sta.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
154 memcpy(reinterpret_cast<char *>(conf.sta.password), ap.get_password().c_str(), ap.get_password().size());
155
156 // The weakest authmode to accept in the fast scan mode
157 if (ap.get_password().empty()) {
158 conf.sta.threshold.authmode = WIFI_AUTH_OPEN;
159 } else {
160 conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK;
161 }
162
163#ifdef USE_WIFI_WPA2_EAP
164 if (ap.get_eap().has_value()) {
165 conf.sta.threshold.authmode = WIFI_AUTH_WPA2_ENTERPRISE;
166 }
167#endif
168
169 if (ap.get_bssid().has_value()) {
170 conf.sta.bssid_set = true;
171 memcpy(conf.sta.bssid, ap.get_bssid()->data(), 6);
172 } else {
173 conf.sta.bssid_set = false;
174 }
175 if (ap.get_channel().has_value()) {
176 conf.sta.channel = *ap.get_channel();
177 conf.sta.scan_method = WIFI_FAST_SCAN;
178 } else {
179 conf.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
180 }
181 // Listen interval for ESP32 station to receive beacon when WIFI_PS_MAX_MODEM is set.
182 // Units: AP beacon intervals. Defaults to 3 if set to 0.
183 conf.sta.listen_interval = 0;
184
185 // Protected Management Frame
186 // Device will prefer to connect in PMF mode if other device also advertises PMF capability.
187 conf.sta.pmf_cfg.capable = true;
188 conf.sta.pmf_cfg.required = false;
189
190 // note, we do our own filtering
191 // The minimum rssi to accept in the fast scan mode
192 conf.sta.threshold.rssi = -127;
193
194 conf.sta.threshold.authmode = WIFI_AUTH_OPEN;
195
196 wifi_config_t current_conf;
197 esp_err_t err;
198 err = esp_wifi_get_config(WIFI_IF_STA, &current_conf);
199 if (err != ERR_OK) {
200 ESP_LOGW(TAG, "esp_wifi_get_config failed: %s", esp_err_to_name(err));
201 // can continue
202 }
203
204 if (memcmp(&current_conf, &conf, sizeof(wifi_config_t)) != 0) { // NOLINT
205 err = esp_wifi_disconnect();
206 if (err != ESP_OK) {
207 ESP_LOGV(TAG, "esp_wifi_disconnect failed: %s", esp_err_to_name(err));
208 return false;
209 }
210 }
211
212 err = esp_wifi_set_config(WIFI_IF_STA, &conf);
213 if (err != ESP_OK) {
214 ESP_LOGV(TAG, "esp_wifi_set_config failed: %s", esp_err_to_name(err));
215 return false;
216 }
217
218 if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
219 return false;
220 }
221
222 // setup enterprise authentication if required
223#ifdef USE_WIFI_WPA2_EAP
224 if (ap.get_eap().has_value()) {
225 // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0.
226 EAPAuth eap = ap.get_eap().value();
227 err = esp_eap_client_set_identity((uint8_t *) eap.identity.c_str(), eap.identity.length());
228 if (err != ESP_OK) {
229 ESP_LOGV(TAG, "esp_eap_client_set_identity failed: %d", err);
230 }
231 int ca_cert_len = strlen(eap.ca_cert);
232 int client_cert_len = strlen(eap.client_cert);
233 int client_key_len = strlen(eap.client_key);
234 if (ca_cert_len) {
235 err = esp_eap_client_set_ca_cert((uint8_t *) eap.ca_cert, ca_cert_len + 1);
236 if (err != ESP_OK) {
237 ESP_LOGV(TAG, "esp_eap_client_set_ca_cert failed: %d", err);
238 }
239 }
240 // workout what type of EAP this is
241 // validation is not required as the config tool has already validated it
242 if (client_cert_len && client_key_len) {
243 // if we have certs, this must be EAP-TLS
244 err = esp_eap_client_set_certificate_and_key((uint8_t *) eap.client_cert, client_cert_len + 1,
245 (uint8_t *) eap.client_key, client_key_len + 1,
246 (uint8_t *) eap.password.c_str(), strlen(eap.password.c_str()));
247 if (err != ESP_OK) {
248 ESP_LOGV(TAG, "esp_eap_client_set_certificate_and_key failed: %d", err);
249 }
250 } else {
251 // in the absence of certs, assume this is username/password based
252 err = esp_eap_client_set_username((uint8_t *) eap.username.c_str(), eap.username.length());
253 if (err != ESP_OK) {
254 ESP_LOGV(TAG, "esp_eap_client_set_username failed: %d", err);
255 }
256 err = esp_eap_client_set_password((uint8_t *) eap.password.c_str(), eap.password.length());
257 if (err != ESP_OK) {
258 ESP_LOGV(TAG, "esp_eap_client_set_password failed: %d", err);
259 }
260 }
261 err = esp_wifi_sta_enterprise_enable();
262 if (err != ESP_OK) {
263 ESP_LOGV(TAG, "esp_wifi_sta_enterprise_enable failed: %d", err);
264 }
265 }
266#endif // USE_WIFI_WPA2_EAP
267
268 this->wifi_apply_hostname_();
269
270 s_sta_connecting = true;
271
272 err = esp_wifi_connect();
273 if (err != ESP_OK) {
274 ESP_LOGW(TAG, "esp_wifi_connect failed: %s", esp_err_to_name(err));
275 return false;
276 }
277
278 return true;
279}
280
282 // enable STA
283 if (!this->wifi_mode_(true, {}))
284 return false;
285
286 // Check if the STA interface is initialized before using it
287 if (s_sta_netif == nullptr) {
288 ESP_LOGW(TAG, "STA interface not initialized");
289 return false;
290 }
291
292 esp_netif_dhcp_status_t dhcp_status;
293 esp_err_t err = esp_netif_dhcpc_get_status(s_sta_netif, &dhcp_status);
294 if (err != ESP_OK) {
295 ESP_LOGV(TAG, "esp_netif_dhcpc_get_status failed: %s", esp_err_to_name(err));
296 return false;
297 }
298
299 if (!manual_ip.has_value()) {
300 // sntp_servermode_dhcp lwip/sntp.c (Required to lock TCPIP core functionality!)
301 // https://github.com/esphome/issues/issues/6591
302 // https://github.com/espressif/arduino-esp32/issues/10526
303 {
304 LwIPLock lock;
305 // lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly,
306 // the built-in SNTP client has a memory leak in certain situations. Disable this feature.
307 // https://github.com/esphome/issues/issues/2299
308 sntp_servermode_dhcp(false);
309 }
310
311 // No manual IP is set; use DHCP client
312 if (dhcp_status != ESP_NETIF_DHCP_STARTED) {
313 err = esp_netif_dhcpc_start(s_sta_netif);
314 if (err != ESP_OK) {
315 ESP_LOGV(TAG, "Starting DHCP client failed: %d", err);
316 }
317 return err == ESP_OK;
318 }
319 return true;
320 }
321
322 esp_netif_ip_info_t info; // struct of ip4_addr_t with ip, netmask, gw
323 info.ip = manual_ip->static_ip;
324 info.gw = manual_ip->gateway;
325 info.netmask = manual_ip->subnet;
326 err = esp_netif_dhcpc_stop(s_sta_netif);
327 if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
328 ESP_LOGV(TAG, "Stopping DHCP client failed: %s", esp_err_to_name(err));
329 }
330
331 err = esp_netif_set_ip_info(s_sta_netif, &info);
332 if (err != ESP_OK) {
333 ESP_LOGV(TAG, "Setting manual IP info failed: %s", esp_err_to_name(err));
334 }
335
336 esp_netif_dns_info_t dns;
337 if (manual_ip->dns1.is_set()) {
338 dns.ip = manual_ip->dns1;
339 esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &dns);
340 }
341 if (manual_ip->dns2.is_set()) {
342 dns.ip = manual_ip->dns2;
343 esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_BACKUP, &dns);
344 }
345
346 return true;
347}
348
350 if (!this->has_sta())
351 return {};
352 network::IPAddresses addresses;
353 esp_netif_ip_info_t ip;
354 esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip);
355 if (err != ESP_OK) {
356 ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err));
357 // TODO: do something smarter
358 // return false;
359 } else {
360 addresses[0] = network::IPAddress(&ip.ip);
361 }
362#if USE_NETWORK_IPV6
363 struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
364 uint8_t count = 0;
365 count = esp_netif_get_all_ip6(s_sta_netif, if_ip6s);
366 assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
367 for (int i = 0; i < count; i++) {
368 addresses[i + 1] = network::IPAddress(&if_ip6s[i]);
369 }
370#endif /* USE_NETWORK_IPV6 */
371 return addresses;
372}
373
375 // setting is done in SYSTEM_EVENT_STA_START callback
376 return true;
377}
378const char *get_auth_mode_str(uint8_t mode) {
379 switch (mode) {
380 case WIFI_AUTH_OPEN:
381 return "OPEN";
382 case WIFI_AUTH_WEP:
383 return "WEP";
384 case WIFI_AUTH_WPA_PSK:
385 return "WPA PSK";
386 case WIFI_AUTH_WPA2_PSK:
387 return "WPA2 PSK";
388 case WIFI_AUTH_WPA_WPA2_PSK:
389 return "WPA/WPA2 PSK";
390 case WIFI_AUTH_WPA2_ENTERPRISE:
391 return "WPA2 Enterprise";
392 case WIFI_AUTH_WPA3_PSK:
393 return "WPA3 PSK";
394 case WIFI_AUTH_WPA2_WPA3_PSK:
395 return "WPA2/WPA3 PSK";
396 case WIFI_AUTH_WAPI_PSK:
397 return "WAPI PSK";
398 default:
399 return "UNKNOWN";
400 }
401}
402
403using esphome_ip4_addr_t = esp_ip4_addr_t;
404
405std::string format_ip4_addr(const esphome_ip4_addr_t &ip) {
406 char buf[20];
407 sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16),
408 uint8_t(ip.addr >> 24));
409 return buf;
410}
411const char *get_op_mode_str(uint8_t mode) {
412 switch (mode) {
413 case WIFI_OFF:
414 return "OFF";
415 case WIFI_STA:
416 return "STA";
417 case WIFI_AP:
418 return "AP";
419 case WIFI_AP_STA:
420 return "AP+STA";
421 default:
422 return "UNKNOWN";
423 }
424}
425const char *get_disconnect_reason_str(uint8_t reason) {
426 switch (reason) {
427 case WIFI_REASON_AUTH_EXPIRE:
428 return "Auth Expired";
429 case WIFI_REASON_AUTH_LEAVE:
430 return "Auth Leave";
431 case WIFI_REASON_ASSOC_EXPIRE:
432 return "Association Expired";
433 case WIFI_REASON_ASSOC_TOOMANY:
434 return "Too Many Associations";
435 case WIFI_REASON_NOT_AUTHED:
436 return "Not Authenticated";
437 case WIFI_REASON_NOT_ASSOCED:
438 return "Not Associated";
439 case WIFI_REASON_ASSOC_LEAVE:
440 return "Association Leave";
441 case WIFI_REASON_ASSOC_NOT_AUTHED:
442 return "Association not Authenticated";
443 case WIFI_REASON_DISASSOC_PWRCAP_BAD:
444 return "Disassociate Power Cap Bad";
445 case WIFI_REASON_DISASSOC_SUPCHAN_BAD:
446 return "Disassociate Supported Channel Bad";
447 case WIFI_REASON_IE_INVALID:
448 return "IE Invalid";
449 case WIFI_REASON_MIC_FAILURE:
450 return "Mic Failure";
451 case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
452 return "4-Way Handshake Timeout";
453 case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT:
454 return "Group Key Update Timeout";
455 case WIFI_REASON_IE_IN_4WAY_DIFFERS:
456 return "IE In 4-Way Handshake Differs";
457 case WIFI_REASON_GROUP_CIPHER_INVALID:
458 return "Group Cipher Invalid";
459 case WIFI_REASON_PAIRWISE_CIPHER_INVALID:
460 return "Pairwise Cipher Invalid";
461 case WIFI_REASON_AKMP_INVALID:
462 return "AKMP Invalid";
463 case WIFI_REASON_UNSUPP_RSN_IE_VERSION:
464 return "Unsupported RSN IE version";
465 case WIFI_REASON_INVALID_RSN_IE_CAP:
466 return "Invalid RSN IE Cap";
467 case WIFI_REASON_802_1X_AUTH_FAILED:
468 return "802.1x Authentication Failed";
469 case WIFI_REASON_CIPHER_SUITE_REJECTED:
470 return "Cipher Suite Rejected";
471 case WIFI_REASON_BEACON_TIMEOUT:
472 return "Beacon Timeout";
473 case WIFI_REASON_NO_AP_FOUND:
474 return "AP Not Found";
475 case WIFI_REASON_AUTH_FAIL:
476 return "Authentication Failed";
477 case WIFI_REASON_ASSOC_FAIL:
478 return "Association Failed";
479 case WIFI_REASON_HANDSHAKE_TIMEOUT:
480 return "Handshake Failed";
481 case WIFI_REASON_CONNECTION_FAIL:
482 return "Connection Failed";
483 case WIFI_REASON_AP_TSF_RESET:
484 return "AP TSF reset";
485 case WIFI_REASON_ROAMING:
486 return "Station Roaming";
487 case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG:
488 return "Association comeback time too long";
489 case WIFI_REASON_SA_QUERY_TIMEOUT:
490 return "SA query timeout";
491 case WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY:
492 return "No AP found with compatible security";
493 case WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD:
494 return "No AP found in auth mode threshold";
495 case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD:
496 return "No AP found in RSSI threshold";
497 case WIFI_REASON_UNSPECIFIED:
498 default:
499 return "Unspecified";
500 }
501}
502
504
505#define ESPHOME_EVENT_ID_WIFI_READY ARDUINO_EVENT_WIFI_READY
506#define ESPHOME_EVENT_ID_WIFI_SCAN_DONE ARDUINO_EVENT_WIFI_SCAN_DONE
507#define ESPHOME_EVENT_ID_WIFI_STA_START ARDUINO_EVENT_WIFI_STA_START
508#define ESPHOME_EVENT_ID_WIFI_STA_STOP ARDUINO_EVENT_WIFI_STA_STOP
509#define ESPHOME_EVENT_ID_WIFI_STA_CONNECTED ARDUINO_EVENT_WIFI_STA_CONNECTED
510#define ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED ARDUINO_EVENT_WIFI_STA_DISCONNECTED
511#define ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE
512#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP ARDUINO_EVENT_WIFI_STA_GOT_IP
513#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6 ARDUINO_EVENT_WIFI_STA_GOT_IP6
514#define ESPHOME_EVENT_ID_WIFI_STA_LOST_IP ARDUINO_EVENT_WIFI_STA_LOST_IP
515#define ESPHOME_EVENT_ID_WIFI_AP_START ARDUINO_EVENT_WIFI_AP_START
516#define ESPHOME_EVENT_ID_WIFI_AP_STOP ARDUINO_EVENT_WIFI_AP_STOP
517#define ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED ARDUINO_EVENT_WIFI_AP_STACONNECTED
518#define ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED ARDUINO_EVENT_WIFI_AP_STADISCONNECTED
519#define ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED
520#define ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED
521#define ESPHOME_EVENT_ID_WIFI_AP_GOT_IP6 ARDUINO_EVENT_WIFI_AP_GOT_IP6
522using esphome_wifi_event_id_t = arduino_event_id_t;
523using esphome_wifi_event_info_t = arduino_event_info_t;
524
526 switch (event) {
527 case ESPHOME_EVENT_ID_WIFI_READY: {
528 ESP_LOGV(TAG, "Ready");
529 break;
530 }
531 case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: {
532 auto it = info.wifi_scan_done;
533 ESP_LOGV(TAG, "Scan done: status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id);
534
536 break;
537 }
538 case ESPHOME_EVENT_ID_WIFI_STA_START: {
539 ESP_LOGV(TAG, "STA start");
540 // apply hostname
541 s_sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
542 esp_err_t err = esp_netif_set_hostname(s_sta_netif, App.get_name().c_str());
543 if (err != ERR_OK) {
544 ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err));
545 }
546 break;
547 }
548 case ESPHOME_EVENT_ID_WIFI_STA_STOP: {
549 ESP_LOGV(TAG, "STA stop");
550 // Clear the STA interface handle to prevent use-after-free
551 s_sta_netif = nullptr;
552 break;
553 }
554 case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
555 auto it = info.wifi_sta_connected;
556 char buf[33];
557 memcpy(buf, it.ssid, it.ssid_len);
558 buf[it.ssid_len] = '\0';
559 ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
560 format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
561#if USE_NETWORK_IPV6
562 this->set_timeout(100, [] { WiFi.enableIPv6(); });
563#endif /* USE_NETWORK_IPV6 */
564
565 break;
566 }
567 case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
568 auto it = info.wifi_sta_disconnected;
569 char buf[33];
570 memcpy(buf, it.ssid, it.ssid_len);
571 buf[it.ssid_len] = '\0';
572 if (it.reason == WIFI_REASON_NO_AP_FOUND) {
573 ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
574 } else {
575 ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
576 format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
577 }
578
579 uint8_t reason = it.reason;
580 if (reason == WIFI_REASON_AUTH_EXPIRE || reason == WIFI_REASON_BEACON_TIMEOUT ||
581 reason == WIFI_REASON_NO_AP_FOUND || reason == WIFI_REASON_ASSOC_FAIL ||
582 reason == WIFI_REASON_HANDSHAKE_TIMEOUT) {
583 err_t err = esp_wifi_disconnect();
584 if (err != ESP_OK) {
585 ESP_LOGV(TAG, "Disconnect failed: %s", esp_err_to_name(err));
586 }
587 this->error_from_callback_ = true;
588 }
589
590 s_sta_connecting = false;
591 break;
592 }
593 case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: {
594 auto it = info.wifi_sta_authmode_change;
595 ESP_LOGV(TAG, "Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode), get_auth_mode_str(it.new_mode));
596 // Mitigate CVE-2020-12638
597 // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors
598 if (it.old_mode != WIFI_AUTH_OPEN && it.new_mode == WIFI_AUTH_OPEN) {
599 ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting");
600 // we can't call retry_connect() from this context, so disconnect immediately
601 // and notify main thread with error_from_callback_
602 err_t err = esp_wifi_disconnect();
603 if (err != ESP_OK) {
604 ESP_LOGW(TAG, "Disconnect failed: %s", esp_err_to_name(err));
605 }
606 this->error_from_callback_ = true;
607 }
608 break;
609 }
610 case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: {
611 auto it = info.got_ip.ip_info;
612 ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(it.ip).c_str(), format_ip4_addr(it.gw).c_str());
613 this->got_ipv4_address_ = true;
614#if USE_NETWORK_IPV6
615 s_sta_connecting = this->num_ipv6_addresses_ < USE_NETWORK_MIN_IPV6_ADDR_COUNT;
616#else
617 s_sta_connecting = false;
618#endif /* USE_NETWORK_IPV6 */
619 break;
620 }
621#if USE_NETWORK_IPV6
622 case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: {
623 auto it = info.got_ip6.ip6_info;
624 ESP_LOGV(TAG, "IPv6 address=" IPV6STR, IPV62STR(it.ip));
625 this->num_ipv6_addresses_++;
626 s_sta_connecting = !(this->got_ipv4_address_ & (this->num_ipv6_addresses_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT));
627 break;
628 }
629#endif /* USE_NETWORK_IPV6 */
630 case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
631 ESP_LOGV(TAG, "Lost IP");
632 this->got_ipv4_address_ = false;
633 break;
634 }
635 case ESPHOME_EVENT_ID_WIFI_AP_START: {
636 ESP_LOGV(TAG, "AP start");
637 break;
638 }
639 case ESPHOME_EVENT_ID_WIFI_AP_STOP: {
640 ESP_LOGV(TAG, "AP stop");
641#ifdef USE_WIFI_AP
642 // Clear the AP interface handle to prevent use-after-free
643 s_ap_netif = nullptr;
644#endif
645 break;
646 }
647 case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
648 auto it = info.wifi_sta_connected;
649 auto &mac = it.bssid;
650 ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
651 break;
652 }
653 case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
654 auto it = info.wifi_sta_disconnected;
655 auto &mac = it.bssid;
656 ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
657 break;
658 }
659 case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
660 ESP_LOGV(TAG, "AP client assigned IP");
661 break;
662 }
663 case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
664 auto it = info.wifi_ap_probereqrecved;
665 ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
666 break;
667 }
668 default:
669 break;
670 }
671}
672
674 const auto status = WiFi.status();
675 if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) {
677 }
678 if (status == WL_NO_SSID_AVAIL) {
680 }
681 if (s_sta_connecting) {
683 }
684 if (status == WL_CONNECTED) {
686 }
688}
690 // enable STA
691 if (!this->wifi_mode_(true, {}))
692 return false;
693
694 // need to use WiFi because of WiFiScanClass allocations :(
695 int16_t err = WiFi.scanNetworks(true, true, passive, 200);
696 if (err != WIFI_SCAN_RUNNING) {
697 ESP_LOGV(TAG, "WiFi.scanNetworks failed: %d", err);
698 return false;
699 }
700
701 return true;
702}
704 this->scan_result_.clear();
705
706 int16_t num = WiFi.scanComplete();
707 if (num < 0)
708 return;
709
710 this->scan_result_.reserve(static_cast<unsigned int>(num));
711 for (int i = 0; i < num; i++) {
712 String ssid = WiFi.SSID(i);
713 wifi_auth_mode_t authmode = WiFi.encryptionType(i);
714 int32_t rssi = WiFi.RSSI(i);
715 uint8_t *bssid = WiFi.BSSID(i);
716 int32_t channel = WiFi.channel(i);
717
718 WiFiScanResult scan({bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]}, std::string(ssid.c_str()),
719 channel, rssi, authmode != WIFI_AUTH_OPEN, ssid.length() == 0);
720 this->scan_result_.push_back(scan);
721 }
722 WiFi.scanDelete();
723 this->scan_done_ = true;
724}
725
726#ifdef USE_WIFI_AP
728 esp_err_t err;
729
730 // enable AP
731 if (!this->wifi_mode_({}, true))
732 return false;
733
734 // Check if the AP interface is initialized before using it
735 if (s_ap_netif == nullptr) {
736 ESP_LOGW(TAG, "AP interface not initialized");
737 return false;
738 }
739
740 esp_netif_ip_info_t info;
741 if (manual_ip.has_value()) {
742 info.ip = manual_ip->static_ip;
743 info.gw = manual_ip->gateway;
744 info.netmask = manual_ip->subnet;
745 } else {
746 info.ip = network::IPAddress(192, 168, 4, 1);
747 info.gw = network::IPAddress(192, 168, 4, 1);
748 info.netmask = network::IPAddress(255, 255, 255, 0);
749 }
750
751 err = esp_netif_dhcps_stop(s_ap_netif);
752 if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
753 ESP_LOGE(TAG, "esp_netif_dhcps_stop failed: %s", esp_err_to_name(err));
754 return false;
755 }
756
757 err = esp_netif_set_ip_info(s_ap_netif, &info);
758 if (err != ESP_OK) {
759 ESP_LOGE(TAG, "esp_netif_set_ip_info failed: %d", err);
760 return false;
761 }
762
763 dhcps_lease_t lease;
764 lease.enable = true;
765 network::IPAddress start_address = network::IPAddress(&info.ip);
766 start_address += 99;
767 lease.start_ip = start_address;
768 ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
769 start_address += 10;
770 lease.end_ip = start_address;
771 ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
772 err = esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease));
773
774 if (err != ESP_OK) {
775 ESP_LOGE(TAG, "esp_netif_dhcps_option failed: %d", err);
776 return false;
777 }
778
779 err = esp_netif_dhcps_start(s_ap_netif);
780
781 if (err != ESP_OK) {
782 ESP_LOGE(TAG, "esp_netif_dhcps_start failed: %d", err);
783 return false;
784 }
785
786 return true;
787}
788
790 // enable AP
791 if (!this->wifi_mode_({}, true))
792 return false;
793
794 wifi_config_t conf;
795 memset(&conf, 0, sizeof(conf));
796 if (ap.get_ssid().size() > sizeof(conf.ap.ssid)) {
797 ESP_LOGE(TAG, "AP SSID too long");
798 return false;
799 }
800 memcpy(reinterpret_cast<char *>(conf.ap.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
801 conf.ap.channel = ap.get_channel().value_or(1);
802 conf.ap.ssid_hidden = ap.get_ssid().size();
803 conf.ap.max_connection = 5;
804 conf.ap.beacon_interval = 100;
805
806 if (ap.get_password().empty()) {
807 conf.ap.authmode = WIFI_AUTH_OPEN;
808 *conf.ap.password = 0;
809 } else {
810 conf.ap.authmode = WIFI_AUTH_WPA2_PSK;
811 if (ap.get_password().size() > sizeof(conf.ap.password)) {
812 ESP_LOGE(TAG, "AP password too long");
813 return false;
814 }
815 memcpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), ap.get_password().size());
816 }
817
818 // pairwise cipher of SoftAP, group cipher will be derived using this.
819 conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP;
820
821 esp_err_t err = esp_wifi_set_config(WIFI_IF_AP, &conf);
822 if (err != ESP_OK) {
823 ESP_LOGV(TAG, "esp_wifi_set_config failed: %d", err);
824 return false;
825 }
826
827 yield();
828
829 if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
830 ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
831 return false;
832 }
833
834 return true;
835}
836
838 esp_netif_ip_info_t ip;
839 esp_netif_get_ip_info(s_ap_netif, &ip);
840 return network::IPAddress(&ip.ip);
841}
842#endif // USE_WIFI_AP
843
844bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); }
845
847 bssid_t bssid{};
848 uint8_t *raw_bssid = WiFi.BSSID();
849 if (raw_bssid != nullptr) {
850 for (size_t i = 0; i < bssid.size(); i++)
851 bssid[i] = raw_bssid[i];
852 }
853 return bssid;
854}
855std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
856int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); }
857int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
861
862} // namespace wifi
863} // namespace esphome
864
865#endif // USE_ESP32_FRAMEWORK_ARDUINO
866#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().
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Helper class to lock the lwIP TCPIP core when making lwIP API calls from non-TCPIP threads.
Definition helpers.h:750
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 set_ap(const WiFiAP &ap)
Setup an Access Point that should be created if no connection to a station can be made.
void set_sta(const WiFiAP &ap)
bool wifi_sta_ip_config_(optional< ManualIP > manual_ip)
std::vector< WiFiScanResult > scan_result_
bool wifi_ap_ip_config_(optional< ManualIP > manual_ip)
void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info)
bool wifi_apply_output_power_(float output_power)
bool wifi_mode_(optional< bool > sta, optional< bool > ap)
mopeka_std_values val[4]
std::array< IPAddress, 5 > IPAddresses
Definition ip_address.h:144
const char *const TAG
Definition spi.cpp:8
arduino_event_info_t esphome_wifi_event_info_t
std::array< uint8_t, 6 > bssid_t
const char * get_auth_mode_str(uint8_t mode)
arduino_event_id_t esphome_wifi_event_id_t
const char * get_disconnect_reason_str(uint8_t reason)
std::string format_ip4_addr(const esphome_ip4_addr_t &ip)
const char * get_op_mode_str(uint8_t mode)
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
bool has_custom_mac_address()
Check if a custom MAC address is set (ESP32 & variants)
Definition helpers.cpp:93
void IRAM_ATTR HOT yield()
Definition core.cpp:27
void set_mac_address(uint8_t *mac)
Set the MAC address to use from the provided byte array (6 bytes).
Definition helpers.cpp:91
std::string format_mac_address_pretty(const uint8_t *mac)
Definition helpers.cpp:244
void get_mac_address_raw(uint8_t *mac)
Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
Definition helpers.cpp:73
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
Application App
Global storage of Application pointer - only one Application can exist.
std::string str() const
Definition ip_address.h:52