Compare commits

...
Author SHA1 Message Date
J. Nick Koston 3340fced89 [web_server] Make web OTA's request handler part of its component
WebServerOTAComponent is now the AsyncWebHandler itself instead of creating a separate handler with new in setup(), so the handler is static storage that memory reports count, with no allocation code and no back pointer.
2026-10-26 18:17:39 -10:00
2 changed files with 62 additions and 77 deletions

No files matched your search

@@ -1,7 +1,6 @@
#include "ota_web_server.h"
#ifdef USE_WEBSERVER_OTA
#include "esphome/components/ota/ota_backend_factory.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
@@ -9,73 +8,37 @@
#include "esphome/components/captive_portal/captive_portal.h"
#endif
#if USE_ESP32
using PlatformString = std::string;
#elif USE_ARDUINO
using PlatformString = String;
#endif
namespace esphome::web_server {
ESPHOME_LOG_TAG(TAG, "web_server.ota");
class OTARequestHandler : public AsyncWebHandler {
public:
OTARequestHandler(WebServerOTAComponent *parent) : parent_(parent) {}
void handleRequest(AsyncWebServerRequest *request) override;
void handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index, uint8_t *data,
size_t len, bool final) override;
bool canHandle(AsyncWebServerRequest *request) const override {
if (request->method() != HTTP_POST)
return false;
// Check if this is an OTA update request
bool WebServerOTAComponent::canHandle(AsyncWebServerRequest *request) const {
if (request->method() != HTTP_POST)
return false;
// Check if this is an OTA update request
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
bool is_ota_request = request->url_to(url_buf) == "/update";
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
bool is_ota_request = request->url_to(url_buf) == "/update";
#else
bool is_ota_request = request->url() == ESPHOME_F("/update");
bool is_ota_request = request->url() == ESPHOME_F("/update");
#endif
#if defined(USE_WEBSERVER_OTA_DISABLED) && defined(USE_CAPTIVE_PORTAL)
// IMPORTANT: USE_WEBSERVER_OTA_DISABLED only disables OTA for the web_server component
// Captive portal can still perform OTA updates - check if request is from active captive portal
// Note: global_captive_portal is the standard way components communicate in ESPHome
return is_ota_request && captive_portal::global_captive_portal != nullptr &&
captive_portal::global_captive_portal->is_active();
// IMPORTANT: USE_WEBSERVER_OTA_DISABLED only disables OTA for the web_server component
// Captive portal can still perform OTA updates - check if request is from active captive portal
// Note: global_captive_portal is the standard way components communicate in ESPHome
return is_ota_request && captive_portal::global_captive_portal != nullptr &&
captive_portal::global_captive_portal->is_active();
#elif defined(USE_WEBSERVER_OTA_DISABLED)
// OTA disabled for web_server and no captive portal compiled in
return false;
// OTA disabled for web_server and no captive portal compiled in
return false;
#else
// OTA enabled for web_server
return is_ota_request;
// OTA enabled for web_server
return is_ota_request;
#endif
}
}
// NOLINTNEXTLINE(readability-identifier-naming)
bool isRequestHandlerTrivial() const override { return false; }
protected:
void report_ota_progress_(AsyncWebServerRequest *request);
void schedule_ota_reboot_();
void ota_init_(const char *filename);
/// Abort and drop the backend, releasing the lock.
void abort_session_();
/// Reply "busy" without touching the running upload.
void refuse_(AsyncWebServerRequest *request);
uint32_t last_ota_progress_{0};
uint32_t ota_read_length_{0};
WebServerOTAComponent *parent_;
bool ota_success_{false};
private:
ota::OTABackendPtr ota_backend_{nullptr};
/// Request that owns ota_backend_.
const AsyncWebServerRequest *ota_request_{nullptr};
const AsyncWebServerRequest *refused_request_{nullptr};
};
void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
void WebServerOTAComponent::report_ota_progress_(AsyncWebServerRequest *request) {
const uint32_t now = millis();
if (now - this->last_ota_progress_ > 1000) {
float percentage = 0.0f;
@@ -91,28 +54,28 @@ void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
}
#ifdef USE_OTA_STATE_LISTENER
// Report progress - use notify_state_deferred_ since we're in web server task
this->parent_->notify_state_deferred_(ota::OTA_IN_PROGRESS, percentage, 0);
this->notify_state_deferred_(ota::OTA_IN_PROGRESS, percentage, 0);
#endif
this->last_ota_progress_ = now;
}
}
void OTARequestHandler::schedule_ota_reboot_() {
void WebServerOTAComponent::schedule_ota_reboot_() {
ESP_LOGI(TAG, "OTA update successful!");
this->parent_->set_timeout(100, []() {
this->set_timeout(100, []() {
ESP_LOGI(TAG, "Performing OTA reboot now");
App.safe_reboot();
});
}
void OTARequestHandler::ota_init_(const char *filename) {
void WebServerOTAComponent::ota_init_(const char *filename) {
ESP_LOGI(TAG, "OTA Update Start: %s", filename);
this->ota_read_length_ = 0;
this->ota_success_ = false;
}
void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index,
uint8_t *data, size_t len, bool final) {
void WebServerOTAComponent::handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index,
uint8_t *data, size_t len, bool final) {
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK;
// First byte of a new upload: index==0 with actual data. (web_server_idf
@@ -130,8 +93,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf
if (!this->ota_backend_) {
ESP_LOGE(TAG, "Failed to create OTA backend");
#ifdef USE_OTA_STATE_LISTENER
this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f,
static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_UNKNOWN));
this->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_UNKNOWN));
#endif
return;
}
@@ -153,14 +115,14 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf
#ifdef USE_OTA_STATE_LISTENER
// Notify OTA started - use notify_state_deferred_ since we're in web server task
this->parent_->notify_state_deferred_(ota::OTA_STARTED, 0.0f, 0);
this->notify_state_deferred_(ota::OTA_STARTED, 0.0f, 0);
#endif
if (error_code != ota::OTA_RESPONSE_OK) {
ESP_LOGE(TAG, "OTA begin failed: %d", error_code);
this->ota_backend_.reset();
#ifdef USE_OTA_STATE_LISTENER
this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
this->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
#endif
return;
}
@@ -188,7 +150,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf
this->ota_backend_.reset();
this->ota_request_ = nullptr;
#ifdef USE_OTA_STATE_LISTENER
this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
this->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
#endif
return;
}
@@ -209,7 +171,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf
this->ota_success_ = true;
#ifdef USE_OTA_STATE_LISTENER
// Report completion before reboot - use notify_state_deferred_ since we're in web server task
this->parent_->notify_state_deferred_(ota::OTA_COMPLETED, 100.0f, 0);
this->notify_state_deferred_(ota::OTA_COMPLETED, 100.0f, 0);
#endif
this->schedule_ota_reboot_();
} else {
@@ -217,7 +179,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf
// Reset the platform updater before the lock is released.
this->ota_backend_->abort();
#ifdef USE_OTA_STATE_LISTENER
this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
this->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
#endif
}
this->ota_backend_.reset();
@@ -225,16 +187,16 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf
}
}
void OTARequestHandler::abort_session_() {
void WebServerOTAComponent::abort_session_() {
this->ota_backend_->abort();
#ifdef USE_OTA_STATE_LISTENER
this->parent_->notify_state_deferred_(ota::OTA_ABORT, 0.0f, 0);
this->notify_state_deferred_(ota::OTA_ABORT, 0.0f, 0);
#endif
this->ota_backend_.reset();
this->ota_request_ = nullptr;
}
void OTARequestHandler::refuse_(AsyncWebServerRequest *request) {
void WebServerOTAComponent::refuse_(AsyncWebServerRequest *request) {
this->refused_request_ = request;
request->onDisconnect([this, request]() {
if (this->refused_request_ == request)
@@ -242,7 +204,7 @@ void OTARequestHandler::refuse_(AsyncWebServerRequest *request) {
});
}
void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
void WebServerOTAComponent::handleRequest(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response;
// Use the ota_success_ flag to determine the actual result
const bool refused = request == this->refused_request_;
@@ -277,8 +239,7 @@ void WebServerOTAComponent::setup() {
return;
}
// The handler lives for the life of the process; WebServerBase never destroys its server
base->add_handler(new OTARequestHandler(this)); // NOLINT
base->add_handler(this);
}
void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); }
@@ -3,20 +3,44 @@
#include "esphome/core/defines.h"
#ifdef USE_WEBSERVER_OTA
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_factory.h"
#include "esphome/components/web_server_base/web_server_base.h"
#include "esphome/core/component.h"
namespace esphome::web_server {
class WebServerOTAComponent final : public ota::OTAComponent {
class WebServerOTAComponent final : public AsyncWebHandler, public ota::OTAComponent {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void handleRequest(AsyncWebServerRequest *request) override;
void handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index, uint8_t *data,
size_t len, bool final) override;
bool canHandle(AsyncWebServerRequest *request) const override;
// NOLINTNEXTLINE(readability-identifier-naming)
bool isRequestHandlerTrivial() const override { return false; }
protected:
friend class OTARequestHandler;
void report_ota_progress_(AsyncWebServerRequest *request);
void schedule_ota_reboot_();
void ota_init_(const char *filename);
/// Abort and drop the backend, releasing the lock.
void abort_session_();
/// Reply "busy" without touching the running upload.
void refuse_(AsyncWebServerRequest *request);
uint32_t last_ota_progress_{0};
uint32_t ota_read_length_{0};
bool ota_success_{false};
private:
ota::OTABackendPtr ota_backend_{nullptr};
/// Request that owns ota_backend_.
const AsyncWebServerRequest *ota_request_{nullptr};
const AsyncWebServerRequest *refused_request_{nullptr};
};
} // namespace esphome::web_server