ESPHome 2025.7.1
Loading...
Searching...
No Matches
ota_web_server.cpp
Go to the documentation of this file.
1#include "ota_web_server.h"
2#ifdef USE_WEBSERVER_OTA
3
6#include "esphome/core/log.h"
7
8#ifdef USE_CAPTIVE_PORTAL
10#endif
11
12#ifdef USE_ARDUINO
13#ifdef USE_ESP8266
14#include <Updater.h>
15#elif defined(USE_ESP32) || defined(USE_LIBRETINY)
16#include <Update.h>
17#endif
18#endif // USE_ARDUINO
19
20namespace esphome {
21namespace web_server {
22
23static const char *const TAG = "web_server.ota";
24
25class OTARequestHandler : public AsyncWebHandler {
26 public:
27 OTARequestHandler(WebServerOTAComponent *parent) : parent_(parent) {}
28 void handleRequest(AsyncWebServerRequest *request) override;
29 void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
30 bool final) override;
31 bool canHandle(AsyncWebServerRequest *request) const override {
32 // Check if this is an OTA update request
33 bool is_ota_request = request->url() == "/update" && request->method() == HTTP_POST;
34
35#if defined(USE_WEBSERVER_OTA_DISABLED) && defined(USE_CAPTIVE_PORTAL)
36 // IMPORTANT: USE_WEBSERVER_OTA_DISABLED only disables OTA for the web_server component
37 // Captive portal can still perform OTA updates - check if request is from active captive portal
38 // Note: global_captive_portal is the standard way components communicate in ESPHome
39 return is_ota_request && captive_portal::global_captive_portal != nullptr &&
41#elif defined(USE_WEBSERVER_OTA_DISABLED)
42 // OTA disabled for web_server and no captive portal compiled in
43 return false;
44#else
45 // OTA enabled for web_server
46 return is_ota_request;
47#endif
48 }
49
50 // NOLINTNEXTLINE(readability-identifier-naming)
51 bool isRequestHandlerTrivial() const override { return false; }
52
53 protected:
54 void report_ota_progress_(AsyncWebServerRequest *request);
55 void schedule_ota_reboot_();
56 void ota_init_(const char *filename);
57
58 uint32_t last_ota_progress_{0};
59 uint32_t ota_read_length_{0};
60 WebServerOTAComponent *parent_;
61 bool ota_success_{false};
62
63 private:
64 std::unique_ptr<ota::OTABackend> ota_backend_{nullptr};
65};
66
67void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
68 const uint32_t now = millis();
69 if (now - this->last_ota_progress_ > 1000) {
70 float percentage = 0.0f;
71 if (request->contentLength() != 0) {
72 // Note: Using contentLength() for progress calculation is technically wrong as it includes
73 // multipart headers/boundaries, but it's only off by a small amount and we don't have
74 // access to the actual firmware size until the upload is complete. This is intentional
75 // as it still gives the user a reasonable progress indication.
76 percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
77 ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
78 } else {
79 ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
80 }
81#ifdef USE_OTA_STATE_CALLBACK
82 // Report progress - use call_deferred since we're in web server task
83 this->parent_->state_callback_.call_deferred(ota::OTA_IN_PROGRESS, percentage, 0);
84#endif
85 this->last_ota_progress_ = now;
86 }
87}
88
89void OTARequestHandler::schedule_ota_reboot_() {
90 ESP_LOGI(TAG, "OTA update successful!");
91 this->parent_->set_timeout(100, []() {
92 ESP_LOGI(TAG, "Performing OTA reboot now");
94 });
95}
96
97void OTARequestHandler::ota_init_(const char *filename) {
98 ESP_LOGI(TAG, "OTA Update Start: %s", filename);
99 this->ota_read_length_ = 0;
100 this->ota_success_ = false;
101}
102
103void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index,
104 uint8_t *data, size_t len, bool final) {
106
107 if (index == 0 && !this->ota_backend_) {
108 // Initialize OTA on first call
109 this->ota_init_(filename.c_str());
110
111#ifdef USE_OTA_STATE_CALLBACK
112 // Notify OTA started - use call_deferred since we're in web server task
113 this->parent_->state_callback_.call_deferred(ota::OTA_STARTED, 0.0f, 0);
114#endif
115
116 // Platform-specific pre-initialization
117#ifdef USE_ARDUINO
118#ifdef USE_ESP8266
119 Update.runAsync(true);
120#endif
121#if defined(USE_ESP32) || defined(USE_LIBRETINY)
122 if (Update.isRunning()) {
123 Update.abort();
124 }
125#endif
126#endif // USE_ARDUINO
127
128 this->ota_backend_ = ota::make_ota_backend();
129 if (!this->ota_backend_) {
130 ESP_LOGE(TAG, "Failed to create OTA backend");
131#ifdef USE_OTA_STATE_CALLBACK
132 this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f,
133 static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_UNKNOWN));
134#endif
135 return;
136 }
137
138 // Web server OTA uses multipart uploads where the actual firmware size
139 // is unknown (contentLength includes multipart overhead)
140 // Pass 0 to indicate unknown size
141 error_code = this->ota_backend_->begin(0);
142 if (error_code != ota::OTA_RESPONSE_OK) {
143 ESP_LOGE(TAG, "OTA begin failed: %d", error_code);
144 this->ota_backend_.reset();
145#ifdef USE_OTA_STATE_CALLBACK
146 this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
147#endif
148 return;
149 }
150 }
151
152 if (!this->ota_backend_) {
153 return;
154 }
155
156 // Process data
157 if (len > 0) {
158 error_code = this->ota_backend_->write(data, len);
159 if (error_code != ota::OTA_RESPONSE_OK) {
160 ESP_LOGE(TAG, "OTA write failed: %d", error_code);
161 this->ota_backend_->abort();
162 this->ota_backend_.reset();
163#ifdef USE_OTA_STATE_CALLBACK
164 this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
165#endif
166 return;
167 }
168 this->ota_read_length_ += len;
169 this->report_ota_progress_(request);
170 }
171
172 // Finalize
173 if (final) {
174 ESP_LOGD(TAG, "OTA final chunk: index=%zu, len=%zu, total_read=%u, contentLength=%zu", index, len,
175 this->ota_read_length_, request->contentLength());
176
177 // For Arduino framework, the Update library tracks expected size from firmware header
178 // If we haven't received enough data, calling end() will fail
179 // This can happen if the upload is interrupted or the client disconnects
180 error_code = this->ota_backend_->end();
181 if (error_code == ota::OTA_RESPONSE_OK) {
182 this->ota_success_ = true;
183#ifdef USE_OTA_STATE_CALLBACK
184 // Report completion before reboot - use call_deferred since we're in web server task
185 this->parent_->state_callback_.call_deferred(ota::OTA_COMPLETED, 100.0f, 0);
186#endif
187 this->schedule_ota_reboot_();
188 } else {
189 ESP_LOGE(TAG, "OTA end failed: %d", error_code);
190#ifdef USE_OTA_STATE_CALLBACK
191 this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
192#endif
193 }
194 this->ota_backend_.reset();
195 }
196}
197
198void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
199 AsyncWebServerResponse *response;
200 // Use the ota_success_ flag to determine the actual result
201 const char *msg = this->ota_success_ ? "Update Successful!" : "Update Failed!";
202 response = request->beginResponse(200, "text/plain", msg);
203 response->addHeader("Connection", "close");
204 request->send(response);
205}
206
208 // Get the global web server base instance and register our handler
210 if (base == nullptr) {
211 ESP_LOGE(TAG, "WebServerBase not found");
212 this->mark_failed();
213 return;
214 }
215
216 // AsyncWebServer takes ownership of the handler and will delete it when the server is destroyed
217 base->add_handler(new OTARequestHandler(this)); // NOLINT
218#ifdef USE_OTA_STATE_CALLBACK
219 // Register with global OTA callback system
221#endif
222}
223
224void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); }
225
226} // namespace web_server
227} // namespace esphome
228
229#endif // USE_WEBSERVER_OTA
virtual void mark_failed()
Mark this component as failed.
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
void call_deferred(ota::OTAState state, float progress, uint8_t error)
Call callbacks with deferral to main loop (for thread safety).
Definition ota_backend.h:83
StateCallbackManager state_callback_
Definition ota_backend.h:91
void add_handler(AsyncWebHandler *handler)
CaptivePortal * global_captive_portal
void register_ota_platform(OTAComponent *ota_caller)
std::unique_ptr< ota::OTABackend > make_ota_backend()
@ OTA_RESPONSE_ERROR_UNKNOWN
Definition ota_backend.h:40
const char *const TAG
Definition spi.cpp:8
WebServerBase * global_web_server_base
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:229
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.