ESPHome  2024.4.1
wifi_component_esp8266.cpp
Go to the documentation of this file.
1 #include "wifi_component.h"
2 #include "esphome/core/defines.h"
3 
4 #ifdef USE_ESP8266
5 
6 #include <user_interface.h>
7 
8 #include <utility>
9 #include <algorithm>
10 #ifdef USE_WIFI_WPA2_EAP
11 #include <wpa2_enterprise.h>
12 #endif
13 
14 extern "C" {
15 #include "lwip/err.h"
16 #include "lwip/dns.h"
17 #include "lwip/dhcp.h"
18 #include "lwip/init.h" // LWIP_VERSION_
19 #include "lwip/apps/sntp.h"
20 #include "lwip/netif.h" // struct netif
21 #include <AddrList.h>
22 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0)
23 #include "LwipDhcpServer.h"
24 #if USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0)
25 #include <ESP8266WiFi.h>
26 #include "ESP8266WiFiAP.h"
27 #define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease)
28 #define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time)
29 #define wifi_softap_set_dhcps_offer_option(offer, mode) dhcpSoftAP.set_dhcps_offer_option(offer, mode)
30 #endif
31 #endif
32 }
33 
34 #include "esphome/core/helpers.h"
35 #include "esphome/core/log.h"
36 #include "esphome/core/hal.h"
37 #include "esphome/core/util.h"
39 
40 namespace esphome {
41 namespace wifi {
42 
43 static const char *const TAG = "wifi_esp8266";
44 
45 static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
46 static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
47 static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
48 static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
49 static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
50 
51 bool WiFiComponent::wifi_mode_(optional<bool> sta, optional<bool> ap) {
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, "Setting WiFi mode failed!");
85  }
86 
87  return ret;
88 }
90  sleep_type_t power_save;
91  switch (this->power_save_) {
93  power_save = LIGHT_SLEEP_T;
94  break;
96  power_save = MODEM_SLEEP_T;
97  break;
99  default:
100  power_save = NONE_SLEEP_T;
101  break;
102  }
103  wifi_fpm_auto_sleep_set_in_null_mode(1);
104  return wifi_set_sleep_type(power_save);
105 }
106 
107 #if LWIP_VERSION_MAJOR != 1
108 /*
109  lwip v2 needs to be notified of IP changes, see also
110  https://github.com/d-a-v/Arduino/blob/0e7d21e17144cfc5f53c016191daca8723e89ee8/libraries/ESP8266WiFi/src/ESP8266WiFiSTA.cpp#L251
111  */
112 #undef netif_set_addr // need to call lwIP-v1.4 netif_set_addr()
113 extern "C" {
114 struct netif *eagle_lwip_getif(int netif_index);
115 void netif_set_addr(struct netif *netif, const ip4_addr_t *ip, const ip4_addr_t *netmask, const ip4_addr_t *gw);
116 };
117 #endif
118 
119 bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
120  // enable STA
121  if (!this->wifi_mode_(true, {}))
122  return false;
123 
124  enum dhcp_status dhcp_status = wifi_station_dhcpc_status();
125  if (!manual_ip.has_value()) {
126  // lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly,
127  // the built-in SNTP client has a memory leak in certain situations. Disable this feature.
128  // https://github.com/esphome/issues/issues/2299
129  sntp_servermode_dhcp(false);
130 
131  // Use DHCP client
132  if (dhcp_status != DHCP_STARTED) {
133  bool ret = wifi_station_dhcpc_start();
134  if (!ret) {
135  ESP_LOGV(TAG, "Starting DHCP client failed!");
136  }
137  return ret;
138  }
139  return true;
140  }
141 
142  bool ret = true;
143 
144 #if LWIP_VERSION_MAJOR != 1
145  // get current->previous IP address
146  // (check below)
147  ip_info previp{};
148  wifi_get_ip_info(STATION_IF, &previp);
149 #endif
150 
151  struct ip_info info {};
152  info.ip = manual_ip->static_ip;
153  info.gw = manual_ip->gateway;
154  info.netmask = manual_ip->subnet;
155 
156  if (dhcp_status == DHCP_STARTED) {
157  bool dhcp_stop_ret = wifi_station_dhcpc_stop();
158  if (!dhcp_stop_ret) {
159  ESP_LOGV(TAG, "Stopping DHCP client failed!");
160  ret = false;
161  }
162  }
163  bool wifi_set_info_ret = wifi_set_ip_info(STATION_IF, &info);
164  if (!wifi_set_info_ret) {
165  ESP_LOGV(TAG, "Setting manual IP info failed!");
166  ret = false;
167  }
168 
169  ip_addr_t dns;
170  if (manual_ip->dns1.is_set()) {
171  dns = manual_ip->dns1;
172  dns_setserver(0, &dns);
173  }
174  if (manual_ip->dns2.is_set()) {
175  dns = manual_ip->dns2;
176  dns_setserver(1, &dns);
177  }
178 
179 #if LWIP_VERSION_MAJOR != 1
180  // trigger address change by calling lwIP-v1.4 api
181  // only when ip is already set by other mean (generally dhcp)
182  if (previp.ip.addr != 0 && previp.ip.addr != info.ip.addr) {
183  netif_set_addr(eagle_lwip_getif(STATION_IF), reinterpret_cast<const ip4_addr_t *>(&info.ip),
184  reinterpret_cast<const ip4_addr_t *>(&info.netmask), reinterpret_cast<const ip4_addr_t *>(&info.gw));
185  }
186 #endif
187  return ret;
188 }
189 
191  if (!this->has_sta())
192  return {};
193  network::IPAddresses addresses;
194  uint8_t index = 0;
195  for (auto &addr : addrList) {
196  addresses[index++] = addr.ipFromNetifNum();
197  }
198  return addresses;
199 }
201  const std::string &hostname = App.get_name();
202  bool ret = wifi_station_set_hostname(const_cast<char *>(hostname.c_str()));
203  if (!ret) {
204  ESP_LOGV(TAG, "Setting WiFi Hostname failed!");
205  }
206 
207  // inform dhcp server of hostname change using dhcp_renew()
208  for (netif *intf = netif_list; intf; intf = intf->next) {
209  // unconditionally update all known interfaces
210 #if LWIP_VERSION_MAJOR == 1
211  intf->hostname = (char *) wifi_station_get_hostname();
212 #else
213  intf->hostname = wifi_station_get_hostname();
214 #endif
215  if (netif_dhcp_data(intf) != nullptr) {
216  // renew already started DHCP leases
217  err_t lwipret = dhcp_renew(intf);
218  if (lwipret != ERR_OK) {
219  ESP_LOGW(TAG, "wifi_apply_hostname_(%s): lwIP error %d on interface %c%c (index %d)", intf->hostname,
220  (int) lwipret, intf->name[0], intf->name[1], intf->num);
221  ret = false;
222  }
223  }
224  }
225 
226  return ret;
227 }
228 
229 bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
230  // enable STA
231  if (!this->wifi_mode_(true, {}))
232  return false;
233 
234  this->wifi_disconnect_();
235 
236  struct station_config conf {};
237  memset(&conf, 0, sizeof(conf));
238  strncpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), sizeof(conf.ssid));
239  strncpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), sizeof(conf.password));
240 
241  if (ap.get_bssid().has_value()) {
242  conf.bssid_set = 1;
243  memcpy(conf.bssid, ap.get_bssid()->data(), 6);
244  } else {
245  conf.bssid_set = 0;
246  }
247 
248 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
249  if (ap.get_password().empty()) {
250  conf.threshold.authmode = AUTH_OPEN;
251  } else {
252  // Only allow auth modes with at least WPA
253  conf.threshold.authmode = AUTH_WPA_PSK;
254  }
255  conf.threshold.rssi = -127;
256 #endif
257 
258  ETS_UART_INTR_DISABLE();
259  bool ret = wifi_station_set_config_current(&conf);
260  ETS_UART_INTR_ENABLE();
261 
262  if (!ret) {
263  ESP_LOGV(TAG, "Setting WiFi Station config failed!");
264  return false;
265  }
266 
267  if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
268  return false;
269  }
270 
271  // setup enterprise authentication if required
272 #ifdef USE_WIFI_WPA2_EAP
273  if (ap.get_eap().has_value()) {
274  // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0.
275  EAPAuth eap = ap.get_eap().value();
276  ret = wifi_station_set_enterprise_identity((uint8_t *) eap.identity.c_str(), eap.identity.length());
277  if (ret) {
278  ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_identity failed! %d", ret);
279  }
280  int ca_cert_len = strlen(eap.ca_cert);
281  int client_cert_len = strlen(eap.client_cert);
282  int client_key_len = strlen(eap.client_key);
283  if (ca_cert_len) {
284  ret = wifi_station_set_enterprise_ca_cert((uint8_t *) eap.ca_cert, ca_cert_len + 1);
285  if (ret) {
286  ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_ca_cert failed! %d", ret);
287  }
288  }
289  // workout what type of EAP this is
290  // validation is not required as the config tool has already validated it
291  if (client_cert_len && client_key_len) {
292  // if we have certs, this must be EAP-TLS
293  ret = wifi_station_set_enterprise_cert_key((uint8_t *) eap.client_cert, client_cert_len + 1,
294  (uint8_t *) eap.client_key, client_key_len + 1,
295  (uint8_t *) eap.password.c_str(), strlen(eap.password.c_str()));
296  if (ret) {
297  ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_cert_key failed! %d", ret);
298  }
299  } else {
300  // in the absence of certs, assume this is username/password based
301  ret = wifi_station_set_enterprise_username((uint8_t *) eap.username.c_str(), eap.username.length());
302  if (ret) {
303  ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_username failed! %d", ret);
304  }
305  ret = wifi_station_set_enterprise_password((uint8_t *) eap.password.c_str(), eap.password.length());
306  if (ret) {
307  ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_password failed! %d", ret);
308  }
309  }
310  ret = wifi_station_set_wpa2_enterprise_auth(true);
311  if (ret) {
312  ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", ret);
313  }
314  }
315 #endif // USE_WIFI_WPA2_EAP
316 
317  this->wifi_apply_hostname_();
318 
319  // Reset flags, do this _before_ wifi_station_connect as the callback method
320  // may be called from wifi_station_connect
321  s_sta_connecting = true;
322  s_sta_connected = false;
323  s_sta_got_ip = false;
324  s_sta_connect_error = false;
325  s_sta_connect_not_found = false;
326 
327  ETS_UART_INTR_DISABLE();
328  ret = wifi_station_connect();
329  ETS_UART_INTR_ENABLE();
330  if (!ret) {
331  ESP_LOGV(TAG, "wifi_station_connect failed!");
332  return false;
333  }
334 
335 #if USE_NETWORK_IPV6
336  bool connected = false;
337  while (!connected) {
338  uint8_t ipv6_addr_count = 0;
339  for (auto addr : addrList) {
340  ESP_LOGV(TAG, "Address %s", addr.toString().c_str());
341  if (addr.isV6()) {
342  ipv6_addr_count++;
343  }
344  }
345  delay(500); // NOLINT
346  connected = (ipv6_addr_count >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
347  }
348 #endif /* USE_NETWORK_IPV6 */
349 
350  if (ap.get_channel().has_value()) {
351  ret = wifi_set_channel(*ap.get_channel());
352  if (!ret) {
353  ESP_LOGV(TAG, "wifi_set_channel failed!");
354  return false;
355  }
356  }
357 
358  return true;
359 }
360 
361 class WiFiMockClass : public ESP8266WiFiGenericClass {
362  public:
363  static void _event_callback(void *event) { ESP8266WiFiGenericClass::_eventCallback(event); } // NOLINT
364 };
365 
366 const LogString *get_auth_mode_str(uint8_t mode) {
367  switch (mode) {
368  case AUTH_OPEN:
369  return LOG_STR("OPEN");
370  case AUTH_WEP:
371  return LOG_STR("WEP");
372  case AUTH_WPA_PSK:
373  return LOG_STR("WPA PSK");
374  case AUTH_WPA2_PSK:
375  return LOG_STR("WPA2 PSK");
376  case AUTH_WPA_WPA2_PSK:
377  return LOG_STR("WPA/WPA2 PSK");
378  default:
379  return LOG_STR("UNKNOWN");
380  }
381 }
382 #ifdef ipv4_addr
383 std::string format_ip_addr(struct ipv4_addr ip) {
384  char buf[20];
385  sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16),
386  uint8_t(ip.addr >> 24));
387  return buf;
388 }
389 #else
390 std::string format_ip_addr(struct ip_addr ip) {
391  char buf[20];
392  sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16),
393  uint8_t(ip.addr >> 24));
394  return buf;
395 }
396 #endif
397 const LogString *get_op_mode_str(uint8_t mode) {
398  switch (mode) {
399  case WIFI_OFF:
400  return LOG_STR("OFF");
401  case WIFI_STA:
402  return LOG_STR("STA");
403  case WIFI_AP:
404  return LOG_STR("AP");
405  case WIFI_AP_STA:
406  return LOG_STR("AP+STA");
407  default:
408  return LOG_STR("UNKNOWN");
409  }
410 }
411 
412 const LogString *get_disconnect_reason_str(uint8_t reason) {
413  /* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the
414  * REASON_* constants aren't continuous, and GCC will fill in the gap with the default value -- wasting 4 bytes of RAM
415  * per entry. As there's ~175 default entries, this wastes 700 bytes of RAM.
416  */
417  if (reason <= REASON_CIPHER_SUITE_REJECTED) { // This must be the last constant with a value <200
418  switch (reason) {
419  case REASON_AUTH_EXPIRE:
420  return LOG_STR("Auth Expired");
421  case REASON_AUTH_LEAVE:
422  return LOG_STR("Auth Leave");
423  case REASON_ASSOC_EXPIRE:
424  return LOG_STR("Association Expired");
425  case REASON_ASSOC_TOOMANY:
426  return LOG_STR("Too Many Associations");
427  case REASON_NOT_AUTHED:
428  return LOG_STR("Not Authenticated");
429  case REASON_NOT_ASSOCED:
430  return LOG_STR("Not Associated");
431  case REASON_ASSOC_LEAVE:
432  return LOG_STR("Association Leave");
433  case REASON_ASSOC_NOT_AUTHED:
434  return LOG_STR("Association not Authenticated");
435  case REASON_DISASSOC_PWRCAP_BAD:
436  return LOG_STR("Disassociate Power Cap Bad");
437  case REASON_DISASSOC_SUPCHAN_BAD:
438  return LOG_STR("Disassociate Supported Channel Bad");
439  case REASON_IE_INVALID:
440  return LOG_STR("IE Invalid");
441  case REASON_MIC_FAILURE:
442  return LOG_STR("Mic Failure");
443  case REASON_4WAY_HANDSHAKE_TIMEOUT:
444  return LOG_STR("4-Way Handshake Timeout");
445  case REASON_GROUP_KEY_UPDATE_TIMEOUT:
446  return LOG_STR("Group Key Update Timeout");
447  case REASON_IE_IN_4WAY_DIFFERS:
448  return LOG_STR("IE In 4-Way Handshake Differs");
449  case REASON_GROUP_CIPHER_INVALID:
450  return LOG_STR("Group Cipher Invalid");
451  case REASON_PAIRWISE_CIPHER_INVALID:
452  return LOG_STR("Pairwise Cipher Invalid");
453  case REASON_AKMP_INVALID:
454  return LOG_STR("AKMP Invalid");
455  case REASON_UNSUPP_RSN_IE_VERSION:
456  return LOG_STR("Unsupported RSN IE version");
457  case REASON_INVALID_RSN_IE_CAP:
458  return LOG_STR("Invalid RSN IE Cap");
459  case REASON_802_1X_AUTH_FAILED:
460  return LOG_STR("802.1x Authentication Failed");
461  case REASON_CIPHER_SUITE_REJECTED:
462  return LOG_STR("Cipher Suite Rejected");
463  }
464  }
465 
466  switch (reason) {
467  case REASON_BEACON_TIMEOUT:
468  return LOG_STR("Beacon Timeout");
469  case REASON_NO_AP_FOUND:
470  return LOG_STR("AP Not Found");
471  case REASON_AUTH_FAIL:
472  return LOG_STR("Authentication Failed");
473  case REASON_ASSOC_FAIL:
474  return LOG_STR("Association Failed");
475  case REASON_HANDSHAKE_TIMEOUT:
476  return LOG_STR("Handshake Failed");
477  case REASON_UNSPECIFIED:
478  default:
479  return LOG_STR("Unspecified");
480  }
481 }
482 
483 void WiFiComponent::wifi_event_callback(System_Event_t *event) {
484  switch (event->event) {
485  case EVENT_STAMODE_CONNECTED: {
486  auto it = event->event_info.connected;
487  char buf[33];
488  memcpy(buf, it.ssid, it.ssid_len);
489  buf[it.ssid_len] = '\0';
490  ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_addr(it.bssid).c_str(),
491  it.channel);
492  s_sta_connected = true;
493  break;
494  }
495  case EVENT_STAMODE_DISCONNECTED: {
496  auto it = event->event_info.disconnected;
497  char buf[33];
498  memcpy(buf, it.ssid, it.ssid_len);
499  buf[it.ssid_len] = '\0';
500  if (it.reason == REASON_NO_AP_FOUND) {
501  ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
502  s_sta_connect_not_found = true;
503  } else {
504  ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
505  format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
506  s_sta_connect_error = true;
507  }
508  s_sta_connected = false;
509  s_sta_connecting = false;
510  break;
511  }
512  case EVENT_STAMODE_AUTHMODE_CHANGE: {
513  auto it = event->event_info.auth_change;
514  ESP_LOGV(TAG, "Event: Changed AuthMode old=%s new=%s", LOG_STR_ARG(get_auth_mode_str(it.old_mode)),
515  LOG_STR_ARG(get_auth_mode_str(it.new_mode)));
516  // Mitigate CVE-2020-12638
517  // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors
518  if (it.old_mode != AUTH_OPEN && it.new_mode == AUTH_OPEN) {
519  ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting...");
520  // we can't call retry_connect() from this context, so disconnect immediately
521  // and notify main thread with error_from_callback_
522  wifi_station_disconnect();
524  }
525  break;
526  }
527  case EVENT_STAMODE_GOT_IP: {
528  auto it = event->event_info.got_ip;
529  ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(),
530  format_ip_addr(it.gw).c_str(), format_ip_addr(it.mask).c_str());
531  s_sta_got_ip = true;
532  break;
533  }
534  case EVENT_STAMODE_DHCP_TIMEOUT: {
535  ESP_LOGW(TAG, "Event: Getting IP address timeout");
536  break;
537  }
538  case EVENT_SOFTAPMODE_STACONNECTED: {
539  auto it = event->event_info.sta_connected;
540  ESP_LOGV(TAG, "Event: AP client connected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
541  break;
542  }
543  case EVENT_SOFTAPMODE_STADISCONNECTED: {
544  auto it = event->event_info.sta_disconnected;
545  ESP_LOGV(TAG, "Event: AP client disconnected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
546  break;
547  }
548  case EVENT_SOFTAPMODE_PROBEREQRECVED: {
549  auto it = event->event_info.ap_probereqrecved;
550  ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
551  break;
552  }
553 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
554  case EVENT_OPMODE_CHANGED: {
555  auto it = event->event_info.opmode_changed;
556  ESP_LOGV(TAG, "Event: Changed Mode old=%s new=%s", LOG_STR_ARG(get_op_mode_str(it.old_opmode)),
557  LOG_STR_ARG(get_op_mode_str(it.new_opmode)));
558  break;
559  }
560  case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: {
561  auto it = event->event_info.distribute_sta_ip;
562  ESP_LOGV(TAG, "Event: AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_addr(it.mac).c_str(),
563  format_ip_addr(it.ip).c_str(), it.aid);
564  break;
565  }
566 #endif
567  default:
568  break;
569  }
570 
571  if (event->event == EVENT_STAMODE_DISCONNECTED) {
573  }
574 
575  WiFiMockClass::_event_callback(event);
576 }
577 
578 bool WiFiComponent::wifi_apply_output_power_(float output_power) {
579  uint8_t val = static_cast<uint8_t>(output_power * 4);
580  system_phy_set_max_tpw(val);
581  return true;
582 }
584  if (!this->wifi_mode_(true, {}))
585  return false;
586 
587  bool ret1, ret2;
588  ETS_UART_INTR_DISABLE();
589  ret1 = wifi_station_set_auto_connect(0);
590  ret2 = wifi_station_set_reconnect_policy(false);
591  ETS_UART_INTR_ENABLE();
592 
593  if (!ret1 || !ret2) {
594  ESP_LOGV(TAG, "Disabling Auto-Connect failed!");
595  }
596 
597  delay(10);
598  return true;
599 }
600 
602  wifi_set_event_handler_cb(&WiFiComponent::wifi_event_callback);
603 
604  // Make sure WiFi is in clean state before anything starts
605  this->wifi_mode_(false, false);
606 }
607 
609  station_status_t status = wifi_station_get_connect_status();
610  switch (status) {
611  case STATION_GOT_IP:
613  case STATION_NO_AP_FOUND:
615  ;
616  case STATION_CONNECT_FAIL:
617  case STATION_WRONG_PASSWORD:
619  case STATION_CONNECTING:
621  case STATION_IDLE:
622  default:
624  }
625 }
626 bool WiFiComponent::wifi_scan_start_(bool passive) {
627  static bool first_scan = false;
628 
629  // enable STA
630  if (!this->wifi_mode_(true, {}))
631  return false;
632 
633  struct scan_config config {};
634  memset(&config, 0, sizeof(config));
635  config.ssid = nullptr;
636  config.bssid = nullptr;
637  config.channel = 0;
638  config.show_hidden = 1;
639 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
640  config.scan_type = passive ? WIFI_SCAN_TYPE_PASSIVE : WIFI_SCAN_TYPE_ACTIVE;
641  if (first_scan) {
642  if (passive) {
643  config.scan_time.passive = 200;
644  } else {
645  config.scan_time.active.min = 100;
646  config.scan_time.active.max = 200;
647  }
648  } else {
649  if (passive) {
650  config.scan_time.passive = 500;
651  } else {
652  config.scan_time.active.min = 400;
653  config.scan_time.active.max = 500;
654  }
655  }
656 #endif
657  first_scan = false;
658  bool ret = wifi_station_scan(&config, &WiFiComponent::s_wifi_scan_done_callback);
659  if (!ret) {
660  ESP_LOGV(TAG, "wifi_station_scan failed!");
661  return false;
662  }
663 
664  return ret;
665 }
667  bool ret = true;
668  // Only call disconnect if interface is up
669  if (wifi_get_opmode() & WIFI_STA)
670  ret = wifi_station_disconnect();
671  station_config conf{};
672  memset(&conf, 0, sizeof(conf));
673  ETS_UART_INTR_DISABLE();
674  wifi_station_set_config_current(&conf);
675  ETS_UART_INTR_ENABLE();
676  return ret;
677 }
680 }
681 
683  this->scan_result_.clear();
684 
685  if (status != OK) {
686  ESP_LOGV(TAG, "Scan failed! %d", status);
687  this->retry_connect();
688  return;
689  }
690  auto *head = reinterpret_cast<bss_info *>(arg);
691  for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) {
692  WiFiScanResult res({it->bssid[0], it->bssid[1], it->bssid[2], it->bssid[3], it->bssid[4], it->bssid[5]},
693  std::string(reinterpret_cast<char *>(it->ssid), it->ssid_len), it->channel, it->rssi,
694  it->authmode != AUTH_OPEN, it->is_hidden != 0);
695  this->scan_result_.push_back(res);
696  }
697  this->scan_done_ = true;
698 }
699 
700 #ifdef USE_WIFI_AP
702  // enable AP
703  if (!this->wifi_mode_({}, true))
704  return false;
705 
706  struct ip_info info {};
707  if (manual_ip.has_value()) {
708  info.ip = manual_ip->static_ip;
709  info.gw = manual_ip->gateway;
710  info.netmask = manual_ip->subnet;
711  } else {
712  info.ip = network::IPAddress(192, 168, 4, 1);
713  info.gw = network::IPAddress(192, 168, 4, 1);
714  info.netmask = network::IPAddress(255, 255, 255, 0);
715  }
716 
717  if (wifi_softap_dhcps_status() == DHCP_STARTED) {
718  if (!wifi_softap_dhcps_stop()) {
719  ESP_LOGV(TAG, "Stopping DHCP server failed!");
720  }
721  }
722 
723  if (!wifi_set_ip_info(SOFTAP_IF, &info)) {
724  ESP_LOGV(TAG, "Setting SoftAP info failed!");
725  return false;
726  }
727 
728 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0)
729  dhcpSoftAP.begin(&info);
730 #endif
731 
732  struct dhcps_lease lease {};
733  lease.enable = true;
734  network::IPAddress start_address = network::IPAddress(&info.ip);
735  start_address += 99;
736  lease.start_ip = start_address;
737  ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
738  start_address += 100;
739  lease.end_ip = start_address;
740  ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
741  if (!wifi_softap_set_dhcps_lease(&lease)) {
742  ESP_LOGV(TAG, "Setting SoftAP DHCP lease failed!");
743  return false;
744  }
745 
746  // lease time 1440 minutes (=24 hours)
747  if (!wifi_softap_set_dhcps_lease_time(1440)) {
748  ESP_LOGV(TAG, "Setting SoftAP DHCP lease time failed!");
749  return false;
750  }
751 
752 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0)
753  ESP8266WiFiClass::softAPDhcpServer().setRouter(true); // send ROUTER option with netif's gateway IP
754 #else
755  uint8_t mode = 1;
756  // bit0, 1 enables router information from ESP8266 SoftAP DHCP server.
757  if (!wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode)) {
758  ESP_LOGV(TAG, "wifi_softap_set_dhcps_offer_option failed!");
759  return false;
760  }
761 #endif
762 
763  if (!wifi_softap_dhcps_start()) {
764  ESP_LOGV(TAG, "Starting SoftAP DHCPS failed!");
765  return false;
766  }
767 
768  return true;
769 }
770 
771 bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
772  // enable AP
773  if (!this->wifi_mode_({}, true))
774  return false;
775 
776  struct softap_config conf {};
777  strncpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), sizeof(conf.ssid));
778  conf.ssid_len = static_cast<uint8>(ap.get_ssid().size());
779  conf.channel = ap.get_channel().value_or(1);
780  conf.ssid_hidden = ap.get_hidden();
781  conf.max_connection = 5;
782  conf.beacon_interval = 100;
783 
784  if (ap.get_password().empty()) {
785  conf.authmode = AUTH_OPEN;
786  *conf.password = 0;
787  } else {
788  conf.authmode = AUTH_WPA2_PSK;
789  strncpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), sizeof(conf.password));
790  }
791 
792  ETS_UART_INTR_DISABLE();
793  bool ret = wifi_softap_set_config_current(&conf);
794  ETS_UART_INTR_ENABLE();
795 
796  if (!ret) {
797  ESP_LOGV(TAG, "wifi_softap_set_config_current failed!");
798  return false;
799  }
800 
801  if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
802  ESP_LOGV(TAG, "wifi_ap_ip_config_ failed!");
803  return false;
804  }
805 
806  return true;
807 }
808 
810  struct ip_info ip {};
811  wifi_get_ip_info(SOFTAP_IF, &ip);
812  return network::IPAddress(&ip.ip);
813 }
814 #endif // USE_WIFI_AP
815 
817  bssid_t bssid{};
818  uint8_t *raw_bssid = WiFi.BSSID();
819  if (raw_bssid != nullptr) {
820  for (size_t i = 0; i < bssid.size(); i++)
821  bssid[i] = raw_bssid[i];
822  }
823  return bssid;
824 }
825 std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
826 int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); }
827 int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); }
828 network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
829 network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
830 network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; }
832 
833 } // namespace wifi
834 } // namespace esphome
835 
836 #endif
std::string format_ip_addr(struct ipv4_addr ip)
std::array< uint8_t, 6 > bssid_t
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)
std::string str() const
Definition: ip_address.h:120
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
const char * get_op_mode_str(uint8_t mode)
std::vector< WiFiScanResult > scan_result_
void netif_set_addr(struct netif *netif, const ip4_addr_t *ip, const ip4_addr_t *netmask, const ip4_addr_t *gw)
const char *const TAG
Definition: spi.cpp:8
struct netif * eagle_lwip_getif(int netif_index)
const optional< ManualIP > & get_manual_ip() const
in_addr ip4_addr_t
Definition: ip_address.h:21
const optional< uint8_t > & get_channel() const
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:151
Application App
Global storage of Application pointer - only one Application can exist.
bool wifi_ap_ip_config_(optional< ManualIP > manual_ip)
WiFiComponent * global_wifi_component
const std::string & get_name() const
Get the name of this Application set by pre_setup().
Definition: application.h:174
std::array< IPAddress, 5 > IPAddresses
Definition: ip_address.h:139
static void s_wifi_scan_done_callback(void *arg, STATUS status)
uint8_t status
Definition: bl0942.h:23
void wifi_scan_done_callback_(void *arg, STATUS status)
const char * get_auth_mode_str(uint8_t mode)
in_addr ip_addr_t
Definition: ip_address.h:20
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
static void wifi_event_callback(System_Event_t *event)
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