mirror of
https://github.com/esphome/esphome.git
synced 2026-10-03 09:46:03 +00:00
[wifi][web_server] Guard AP portal helpers with USE_WIFI_AP and apply review cleanups
This commit is contained in:
@@ -74,7 +74,6 @@ def _final_validate(config: ConfigType) -> None:
|
||||
"Add 'ap:' to your WiFi configuration to enable the captive portal."
|
||||
)
|
||||
|
||||
# The listening socket is registered by web_server_base (shared HTTP server).
|
||||
web_server_base.consume_captive_dns_sockets(config, "captive_portal")
|
||||
|
||||
|
||||
|
||||
@@ -378,11 +378,18 @@ def _final_validate_ap_mode(config: ConfigType) -> None:
|
||||
wifi_config = full_config.get(CONF_WIFI)
|
||||
if serve_captive(config, full_config):
|
||||
web_server_base.consume_captive_dns_sockets(config, "web_server")
|
||||
if CONF_LOCAL not in config:
|
||||
_LOGGER.info(
|
||||
"WiFi is AP only: embedding the web interface in the firmware "
|
||||
"(local: true) and serving it as a captive portal on the access point. "
|
||||
"Set 'local: false' to load it from the internet instead."
|
||||
)
|
||||
elif wifi_is_ap_only(wifi_config) and not serve_local(config, wifi_config):
|
||||
_LOGGER.warning(
|
||||
"WiFi is AP only and web_server has local: false, so the web interface is "
|
||||
"loaded from the internet, which browsers on the access point usually cannot "
|
||||
"reach; the page stays blank. Remove 'local: false' to embed it in the firmware."
|
||||
"WiFi is AP only and the web_server interface is loaded from the internet, "
|
||||
"which browsers on the access point usually cannot reach; the page stays "
|
||||
"blank. Remove 'local: false', or migrate off version 1, so the interface "
|
||||
"is embedded in the firmware."
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -346,12 +346,14 @@ void DeferredUpdateEventSourceList::on_client_disconnect_(DeferredUpdateEventSou
|
||||
|
||||
#ifdef USE_WEBSERVER_CAPTIVE
|
||||
WebServer *global_web_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
WebServer::WebServer(web_server_base::WebServerBase *base) : base_(base) { global_web_server = this; }
|
||||
#else
|
||||
WebServer::WebServer(web_server_base::WebServerBase *base) : base_(base) {}
|
||||
#endif
|
||||
|
||||
WebServer::WebServer(web_server_base::WebServerBase *base) : base_(base) {
|
||||
#ifdef USE_WEBSERVER_CAPTIVE
|
||||
global_web_server = this;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; }
|
||||
#endif
|
||||
@@ -399,8 +401,7 @@ void WebServer::setup() {
|
||||
#ifdef USE_WEBSERVER_CAPTIVE
|
||||
// Not-found fallback (outside the auth middleware): the OS captive portal probes hit
|
||||
// arbitrary URLs and must get the redirect without credentials.
|
||||
this->base_->get_server()->onNotFound(
|
||||
[](AsyncWebServerRequest *request) { global_web_server->handle_not_found_(request); });
|
||||
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *request) { this->handle_not_found_(request); });
|
||||
#endif
|
||||
|
||||
// OTA is now handled by the web_server OTA platform
|
||||
@@ -417,25 +418,24 @@ void WebServer::setup() {
|
||||
});
|
||||
}
|
||||
void WebServer::loop() {
|
||||
bool has_clients = this->events_.loop();
|
||||
bool keep_looping = this->events_.loop();
|
||||
#ifdef USE_WEBSERVER_CAPTIVE
|
||||
if (this->dns_.is_running()) {
|
||||
this->dns_.loop();
|
||||
return;
|
||||
}
|
||||
this->dns_.loop();
|
||||
keep_looping |= this->dns_.is_running();
|
||||
#endif
|
||||
// No SSE clients connected; stop looping until a new client connects via
|
||||
// No SSE clients connected (and no captive DNS to serve); stop looping until a new client connects via
|
||||
// enable_loop_soon_any_context(). This is safe because:
|
||||
// - set_interval/set_timeout/defer run via the Scheduler, independent of loop()
|
||||
// - deferrable_send_state early-outs when no clients are connected
|
||||
// - try_send_nodefer (log, ping) iterates sessions which are empty
|
||||
// - REST API handlers use defer() which runs via the Scheduler
|
||||
if (!has_clients)
|
||||
if (!keep_looping)
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
#ifdef USE_WEBSERVER_CAPTIVE
|
||||
void WebServer::start_captive() {
|
||||
// CaptiveDNS::start() no-ops too; this guard just avoids repeating the log and enable_loop
|
||||
if (this->dns_.is_running())
|
||||
return;
|
||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
|
||||
@@ -282,8 +282,9 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#ifdef USE_WEBSERVER_CAPTIVE
|
||||
/** AP mode: run a DNS server that answers every name with the AP address and redirect any
|
||||
* unknown URL to the interface, so a phone joining the AP opens it through the OS captive
|
||||
* portal check. Started and ended by the wifi component with the access point; start may
|
||||
* run before setup(), so it touches nothing but the DNS server.
|
||||
* portal check. Started and ended by the wifi component with the access point. start may run
|
||||
* before setup() (wifi sets up first): safe because enable_loop() is a no-op before setup;
|
||||
* nothing but the DNS server may be touched, in particular not base_ or the handlers.
|
||||
*/
|
||||
void start_captive();
|
||||
void end_captive();
|
||||
|
||||
@@ -1619,7 +1619,9 @@ void WiFiComponent::check_connecting_finished(uint32_t now) {
|
||||
this->retry_phase_ = WiFiRetryPhase::INITIAL_CONNECT;
|
||||
this->num_retried_ = 0;
|
||||
if (this->has_ap()) {
|
||||
#ifdef USE_WIFI_AP
|
||||
this->end_ap_portal_();
|
||||
#endif
|
||||
ESP_LOGD(TAG, "Disabling AP");
|
||||
this->wifi_mode_({}, false);
|
||||
}
|
||||
@@ -2219,6 +2221,9 @@ bool WiFiComponent::is_ap_portal_active_() {
|
||||
return this->is_captive_portal_active_();
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
// global_web_server needs no null check: codegen always instantiates WebServer when
|
||||
// USE_WEBSERVER_CAPTIVE is defined, and the constructor assigns the global.
|
||||
void WiFiComponent::start_ap_portal_() {
|
||||
#ifdef USE_CAPTIVE_PORTAL
|
||||
if (captive_portal::global_captive_portal != nullptr)
|
||||
@@ -2238,6 +2243,7 @@ void WiFiComponent::end_ap_portal_() {
|
||||
web_server::global_web_server->end_captive();
|
||||
#endif
|
||||
}
|
||||
#endif // USE_WIFI_AP
|
||||
bool WiFiComponent::is_esp32_improv_active_() {
|
||||
#ifdef USE_IMPROV
|
||||
return esp32_improv::global_improv_component != nullptr && esp32_improv::global_improv_component->is_active();
|
||||
|
||||
@@ -791,8 +791,10 @@ class WiFiComponent final : public Component {
|
||||
bool is_captive_portal_active_();
|
||||
/// captive_portal or the web_server AP mode is serving a user on the access point
|
||||
bool is_ap_portal_active_();
|
||||
#ifdef USE_WIFI_AP
|
||||
void start_ap_portal_();
|
||||
void end_ap_portal_();
|
||||
#endif
|
||||
bool is_esp32_improv_active_();
|
||||
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
|
||||
@@ -1131,10 +1131,11 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
|
||||
#if (defined(USE_CAPTIVE_PORTAL) || defined(USE_WEBSERVER_CAPTIVE)) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)
|
||||
// Configure DHCP Option 114 (Captive Portal URI) if captive portal or the web_server AP
|
||||
// mode is enabled. This provides a standards-compliant way for clients to discover the portal
|
||||
#ifdef USE_CAPTIVE_PORTAL
|
||||
const bool has_portal = captive_portal::global_captive_portal != nullptr;
|
||||
#else
|
||||
#ifdef USE_WEBSERVER_CAPTIVE
|
||||
// web_server AP mode always serves the portal when compiled in
|
||||
const bool has_portal = true;
|
||||
#else
|
||||
const bool has_portal = captive_portal::global_captive_portal != nullptr;
|
||||
#endif
|
||||
if (has_portal) {
|
||||
// Buffer must be static - dhcps_set_option_info stores pointer, doesn't copy
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# AP mode: with a WiFi access point and the interface embedded in the firmware, web_server
|
||||
# runs its own captive portal (DNS server, unknown URLs redirect to the page).
|
||||
wifi:
|
||||
ap:
|
||||
ssid: "ESPHome-Test"
|
||||
password: "Test1234!"
|
||||
|
||||
web_server:
|
||||
local: true
|
||||
@@ -1,9 +1,2 @@
|
||||
# AP mode: with a WiFi access point and the interface embedded in the firmware, web_server
|
||||
# runs its own captive portal (DNS server, unknown URLs redirect to the page).
|
||||
wifi:
|
||||
ap:
|
||||
ssid: "ESPHome-Test"
|
||||
password: "Test1234!"
|
||||
|
||||
web_server:
|
||||
local: true
|
||||
packages:
|
||||
web_server: !include common-ap-mode.yaml
|
||||
|
||||
@@ -1,9 +1,2 @@
|
||||
# AP mode: with a WiFi access point and the interface embedded in the firmware, web_server
|
||||
# runs its own captive portal (DNS server, unknown URLs redirect to the page).
|
||||
wifi:
|
||||
ap:
|
||||
ssid: "ESPHome-Test"
|
||||
password: "Test1234!"
|
||||
|
||||
web_server:
|
||||
local: true
|
||||
packages:
|
||||
web_server: !include common-ap-mode.yaml
|
||||
|
||||
Reference in New Issue
Block a user