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