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