ESPHome 2025.12.2
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
8namespace esphome {
9namespace captive_portal {
10
11static const char *const TAG = "captive_portal";
12
13void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
14 AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("application/json"));
15 stream->addHeader(ESPHOME_F("cache-control"), ESPHOME_F("public, max-age=0, must-revalidate"));
16 char mac_s[18];
17 const char *mac_str = get_mac_address_pretty_into_buffer(mac_s);
18#ifdef USE_ESP8266
19 stream->print(ESPHOME_F("{\"mac\":\""));
20 stream->print(mac_str);
21 stream->print(ESPHOME_F("\",\"name\":\""));
22 stream->print(App.get_name().c_str());
23 stream->print(ESPHOME_F("\",\"aps\":[{}"));
24#else
25 stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", mac_str, App.get_name().c_str());
26#endif
27
28 for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
29 if (scan.get_is_hidden())
30 continue;
31
32 // Assumes no " in ssid, possible unicode isses?
33#ifdef USE_ESP8266
34 stream->print(ESPHOME_F(",{\"ssid\":\""));
35 stream->print(scan.get_ssid().c_str());
36 stream->print(ESPHOME_F("\",\"rssi\":"));
37 stream->print(scan.get_rssi());
38 stream->print(ESPHOME_F(",\"lock\":"));
39 stream->print(scan.get_with_auth());
40 stream->print(ESPHOME_F("}"));
41#else
42 stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(),
43 scan.get_with_auth());
44#endif
45 }
46 stream->print(ESPHOME_F("]}"));
47 request->send(stream);
48}
49void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
50 std::string ssid = request->arg("ssid").c_str(); // NOLINT(readability-redundant-string-cstr)
51 std::string psk = request->arg("psk").c_str(); // NOLINT(readability-redundant-string-cstr)
52 ESP_LOGI(TAG, "Requested WiFi Settings Change:");
53 ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
54 ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
55 // Defer save to main loop thread to avoid NVS operations from HTTP thread
56 this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); });
57 request->redirect(ESPHOME_F("/?save"));
58}
59
61 // Disable loop by default - will be enabled when captive portal starts
62 this->disable_loop();
63}
65 this->base_->init();
66 if (!this->initialized_) {
67 this->base_->add_handler(this);
68 }
69
71
72#ifdef USE_ESP_IDF
73 // Create DNS server instance for ESP-IDF
74 this->dns_server_ = make_unique<DNSServer>();
75 this->dns_server_->start(ip);
76#endif
77#ifdef USE_ARDUINO
78 this->dns_server_ = make_unique<DNSServer>();
79 this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
80 this->dns_server_->start(53, ESPHOME_F("*"), ip);
81#endif
82
83 this->initialized_ = true;
84 this->active_ = true;
85
86 // Enable loop() now that captive portal is active
87 this->enable_loop();
88
89 ESP_LOGV(TAG, "Captive portal started");
90}
91
92void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
93 if (req->url() == ESPHOME_F("/config.json")) {
94 this->handle_config(req);
95 return;
96 } else if (req->url() == ESPHOME_F("/wifisave")) {
97 this->handle_wifisave(req);
98 return;
99 }
100
101 // All other requests get the captive portal page
102 // This includes OS captive portal detection endpoints which will trigger
103 // the captive portal when they don't receive their expected responses
104#ifndef USE_ESP8266
105 auto *response = req->beginResponse(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
106#else
107 auto *response = req->beginResponse_P(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
108#endif
109 response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip"));
110 req->send(response);
111}
112
115 // Before WiFi
116 return setup_priority::WIFI + 1.0f;
117}
118void CaptivePortal::dump_config() { ESP_LOGCONFIG(TAG, "Captive Portal:"); }
119
120CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
121
122} // namespace captive_portal
123} // namespace esphome
124#endif
const std::string & get_name() const
Get the name of this Application set by pre_setup().
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
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(AsyncWebHandler *handler)
void save_wifi_sta(const std::string &ssid, const std::string &password)
CaptivePortal * global_captive_portal
WiFiComponent * global_wifi_component
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
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:669
Application App
Global storage of Application pointer - only one Application can exist.