ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
captive_portal.cpp
Go to the documentation of this file.
1#include "captive_portal.h"
2#ifdef USE_CAPTIVE_PORTAL
3#include "esphome/core/log.h"
6#include "captive_index.h"
7
9
10static const char *const TAG = "captive_portal";
11
12void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
13 AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("application/json"));
14 stream->addHeader(ESPHOME_F("cache-control"), ESPHOME_F("public, max-age=0, must-revalidate"));
15 char mac_s[18];
16 const char *mac_str = get_mac_address_pretty_into_buffer(mac_s);
17#ifdef USE_ESP8266
18 stream->print(ESPHOME_F("{\"mac\":\""));
19 stream->print(mac_str);
20 stream->print(ESPHOME_F("\",\"name\":\""));
21 stream->print(App.get_name().c_str());
22 stream->print(ESPHOME_F("\",\"aps\":[{}"));
23#else
24 stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", mac_str, App.get_name().c_str());
25#endif
26
27 for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
28 if (scan.get_is_hidden())
29 continue;
30
31 // Assumes no " in ssid, possible unicode isses?
32#ifdef USE_ESP8266
33 stream->print(ESPHOME_F(",{\"ssid\":\""));
34 stream->print(scan.get_ssid().c_str());
35 stream->print(ESPHOME_F("\",\"rssi\":"));
36 stream->print(scan.get_rssi());
37 stream->print(ESPHOME_F(",\"lock\":"));
38 stream->print(scan.get_with_auth());
39 stream->print(ESPHOME_F("}"));
40#else
41 stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(),
42 scan.get_with_auth());
43#endif
44 }
45 stream->print(ESPHOME_F("]}"));
46 request->send(stream);
47}
48void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
49 const auto &ssid = request->arg("ssid");
50 const auto &psk = request->arg("psk");
51 ESP_LOGI(TAG,
52 "Requested WiFi Settings Change:\n"
53 " SSID='%s'\n"
54 " Password=" LOG_SECRET("'%s'"),
55 ssid.c_str(), psk.c_str());
56#ifdef USE_ESP8266
57 // ESP8266 is single-threaded, call directly
58 wifi::global_wifi_component->save_wifi_sta(ssid.c_str(), psk.c_str());
59#else
60 // Defer save to main loop thread to avoid NVS operations from HTTP thread
61 this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid.c_str(), psk.c_str()); });
62#endif
63 request->send(200, ESPHOME_F("text/plain"), ESPHOME_F("Saved. Connecting..."));
64}
65
67 // Disable loop by default - will be enabled when captive portal starts
68 this->disable_loop();
69}
71 this->base_->init();
72 if (!this->initialized_) {
74 }
75
77
78#if defined(USE_ESP32)
79 // Create DNS server instance for ESP-IDF
80 this->dns_server_ = make_unique<DNSServer>();
81 this->dns_server_->start(ip);
82#elif defined(USE_ARDUINO)
83 this->dns_server_ = make_unique<DNSServer>();
84 this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
85 this->dns_server_->start(53, ESPHOME_F("*"), ip);
86#endif
87
88 this->initialized_ = true;
89 this->active_ = true;
90
91 // Enable loop() now that captive portal is active
92 this->enable_loop();
93
94 ESP_LOGV(TAG, "Captive portal started");
95}
96
97void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
98#ifdef USE_ESP32
99 char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
100 StringRef url = req->url_to(url_buf);
101#else
102 const auto &url = req->url();
103#endif
104 if (url == ESPHOME_F("/config.json")) {
105 this->handle_config(req);
106 return;
107 } else if (url == ESPHOME_F("/wifisave")) {
108 this->handle_wifisave(req);
109 return;
110 }
111
112 // All other requests get the captive portal page
113 // This includes OS captive portal detection endpoints which will trigger
114 // the captive portal when they don't receive their expected responses
115#ifndef USE_ESP8266
116 auto *response = req->beginResponse(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
117#else
118 auto *response = req->beginResponse_P(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
119#endif
120#ifdef USE_CAPTIVE_PORTAL_GZIP
121 response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip"));
122#else
123 response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("br"));
124#endif
125 req->send(response);
126}
127
130 // Before WiFi
131 return setup_priority::WIFI + 1.0f;
132}
133void CaptivePortal::dump_config() { ESP_LOGCONFIG(TAG, "Captive Portal:"); }
134
135CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
136
137} // namespace esphome::captive_portal
138
139#endif
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std voi defer)(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition component.h:543
void enable_loop()
Enable this component's loop.
Definition component.h:246
void disable_loop()
Disable this component's loop.
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
constexpr const char * c_str() const
Definition string_ref.h:73
std::unique_ptr< DNSServer > dns_server_
CaptivePortal(web_server_base::WebServerBase *base)
void handle_config(AsyncWebServerRequest *request)
web_server_base::WebServerBase * base_
void handleRequest(AsyncWebServerRequest *req) override
void handle_wifisave(AsyncWebServerRequest *request)
void add_handler_without_auth(AsyncWebHandler *handler)
WARNING: Registers a handler that bypasses the USE_WEBSERVER_AUTH middleware.
void save_wifi_sta(const std::string &ssid, const std::string &password)
CaptivePortal * global_captive_portal
constexpr float WIFI
Definition component.h:48
WiFiComponent * global_wifi_component
const char * get_mac_address_pretty_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in colon-separated uppercase hex notation.
Definition helpers.cpp:750
Application App
Global storage of Application pointer - only one Application can exist.