ESPHome 2025.12.5
Loading...
Searching...
No Matches
ota_http_request.cpp
Go to the documentation of this file.
1#include "ota_http_request.h"
2
5#include "esphome/core/log.h"
6
13
14namespace esphome {
15namespace http_request {
16
17static const char *const TAG = "http_request.ota";
18
20#ifdef USE_OTA_STATE_CALLBACK
22#endif
23}
24
25void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); };
26
27void OtaHttpRequestComponent::set_md5_url(const std::string &url) {
28 if (!this->validate_url_(url)) {
29 this->md5_url_.clear(); // URL was not valid; prevent flashing until it is
30 return;
31 }
32 this->md5_url_ = url;
33 this->md5_expected_.clear(); // to be retrieved later
34}
35
36void OtaHttpRequestComponent::set_url(const std::string &url) {
37 if (!this->validate_url_(url)) {
38 this->url_.clear(); // URL was not valid; prevent flashing until it is
39 return;
40 }
41 this->url_ = url;
42}
43
45 if (this->url_.empty()) {
46 ESP_LOGE(TAG, "URL not set; cannot start update");
47 return;
48 }
49
50 ESP_LOGI(TAG, "Starting update");
51#ifdef USE_OTA_STATE_CALLBACK
52 this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
53#endif
54
55 auto ota_status = this->do_ota_();
56
57 switch (ota_status) {
59#ifdef USE_OTA_STATE_CALLBACK
60 this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status);
61#endif
62 delay(10);
64 break;
65
66 default:
67#ifdef USE_OTA_STATE_CALLBACK
68 this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status);
69#endif
70 this->md5_computed_.clear(); // will be reset at next attempt
71 this->md5_expected_.clear(); // will be reset at next attempt
72 break;
73 }
74}
75
76void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend,
77 const std::shared_ptr<HttpContainer> &container) {
78 if (this->update_started_) {
79 ESP_LOGV(TAG, "Aborting OTA backend");
80 backend->abort();
81 }
82 ESP_LOGV(TAG, "Aborting HTTP connection");
83 container->end();
84};
85
88 uint32_t last_progress = 0;
89 uint32_t update_start_time = millis();
90 md5::MD5Digest md5_receive;
91 std::unique_ptr<char[]> md5_receive_str(new char[33]);
92
93 if (this->md5_expected_.empty() && !this->http_get_md5_()) {
94 return OTA_MD5_INVALID;
95 }
96
97 ESP_LOGD(TAG, "MD5 expected: %s", this->md5_expected_.c_str());
98
99 auto url_with_auth = this->get_url_with_auth_(this->url_);
100 if (url_with_auth.empty()) {
101 return OTA_BAD_URL;
102 }
103 ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
104 ESP_LOGI(TAG, "Connecting to: %s", this->url_.c_str());
105
106 auto container = this->parent_->get(url_with_auth);
107
108 if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
110 }
111
112 // we will compute MD5 on the fly for verification -- Arduino OTA seems to ignore it
113 md5_receive.init();
114 ESP_LOGV(TAG, "MD5Digest initialized");
115
116 ESP_LOGV(TAG, "OTA backend begin");
117 auto backend = ota::make_ota_backend();
118 auto error_code = backend->begin(container->content_length);
119 if (error_code != ota::OTA_RESPONSE_OK) {
120 ESP_LOGW(TAG, "backend->begin error: %d", error_code);
121 this->cleanup_(std::move(backend), container);
122 return error_code;
123 }
124
125 while (container->get_bytes_read() < container->content_length) {
126 // read a maximum of chunk_size bytes into buf. (real read size returned)
127 int bufsize = container->read(buf, OtaHttpRequestComponent::HTTP_RECV_BUFFER);
128 ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize = %i", container->get_bytes_read(),
129 container->content_length, bufsize);
130
131 // feed watchdog and give other tasks a chance to run
132 App.feed_wdt();
133 yield();
134
135 // Exit loop if no data available (stream closed or end of data)
136 if (bufsize <= 0) {
137 if (bufsize < 0) {
138 ESP_LOGE(TAG, "Stream closed with error");
139 this->cleanup_(std::move(backend), container);
141 }
142 // bufsize == 0: no more data available, exit loop
143 break;
144 }
145
147 // add read bytes to MD5
148 md5_receive.add(buf, bufsize);
149
150 // write bytes to OTA backend
151 this->update_started_ = true;
152 error_code = backend->write(buf, bufsize);
153 if (error_code != ota::OTA_RESPONSE_OK) {
154 // error code explanation available at
155 // https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h
156 ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
157 container->get_bytes_read() - bufsize, container->content_length);
158 this->cleanup_(std::move(backend), container);
159 return error_code;
160 }
161 }
162
163 uint32_t now = millis();
164 if ((now - last_progress > 1000) or (container->get_bytes_read() == container->content_length)) {
165 last_progress = now;
166 float percentage = container->get_bytes_read() * 100.0f / container->content_length;
167 ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
168#ifdef USE_OTA_STATE_CALLBACK
169 this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
170#endif
171 }
172 } // while
173
174 ESP_LOGI(TAG, "Done in %.0f seconds", float(millis() - update_start_time) / 1000);
175
176 // verify MD5 is as expected and act accordingly
177 md5_receive.calculate();
178 md5_receive.get_hex(md5_receive_str.get());
179 this->md5_computed_ = md5_receive_str.get();
180 if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
181 ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
182 this->cleanup_(std::move(backend), container);
184 } else {
185 backend->set_update_md5(md5_receive_str.get());
186 }
187
188 container->end();
189
190 // feed watchdog and give other tasks a chance to run
191 App.feed_wdt();
192 yield();
193 delay(100); // NOLINT
194
195 error_code = backend->end();
196 if (error_code != ota::OTA_RESPONSE_OK) {
197 ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
198 this->cleanup_(std::move(backend), container);
199 return error_code;
200 }
201
202 ESP_LOGI(TAG, "Update complete");
204}
205
206std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) {
207 if (this->username_.empty() || this->password_.empty()) {
208 return url;
209 }
210
211 auto start_char = url.find("://");
212 if ((start_char == std::string::npos) || (start_char < 4)) {
213 ESP_LOGE(TAG, "Incorrect URL prefix");
214 return {};
215 }
216
217 ESP_LOGD(TAG, "Using basic HTTP authentication");
218
219 start_char += 3; // skip '://' characters
220 auto url_with_auth =
221 url.substr(0, start_char) + this->username_ + ":" + this->password_ + "@" + url.substr(start_char);
222 return url_with_auth;
223}
224
226 if (this->md5_url_.empty()) {
227 return false;
228 }
229
230 auto url_with_auth = this->get_url_with_auth_(this->md5_url_);
231 if (url_with_auth.empty()) {
232 return false;
233 }
234
235 ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
236 ESP_LOGI(TAG, "Connecting to: %s", this->md5_url_.c_str());
237 auto container = this->parent_->get(url_with_auth);
238 if (container == nullptr) {
239 ESP_LOGE(TAG, "Failed to connect to MD5 URL");
240 return false;
241 }
242 size_t length = container->content_length;
243 if (length == 0) {
244 container->end();
245 return false;
246 }
247 if (length < MD5_SIZE) {
248 ESP_LOGE(TAG, "MD5 file must be %u bytes; %u bytes reported by HTTP server. Aborting", MD5_SIZE, length);
249 container->end();
250 return false;
251 }
252
253 this->md5_expected_.resize(MD5_SIZE);
254 int read_len = 0;
255 while (container->get_bytes_read() < MD5_SIZE) {
256 read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
257 if (read_len <= 0) {
258 break;
259 }
260 App.feed_wdt();
261 yield();
262 }
263 container->end();
264
265 ESP_LOGV(TAG, "Read len: %u, MD5 expected: %u", read_len, MD5_SIZE);
266 return read_len == MD5_SIZE;
267}
268
269bool OtaHttpRequestComponent::validate_url_(const std::string &url) {
270 if ((url.length() < 8) || !url.starts_with("http") || (url.find("://") == std::string::npos)) {
271 ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'");
272 return false;
273 }
274 return true;
275}
276
277} // namespace http_request
278} // namespace esphome
void feed_wdt(uint32_t time=0)
void get_hex(char *output)
Retrieve the hash as hex characters.
Definition hash_base.h:29
void cleanup_(std::unique_ptr< ota::OTABackend > backend, const std::shared_ptr< HttpContainer > &container)
std::string get_url_with_auth_(const std::string &url)
void set_md5_url(const std::string &md5_url)
void calculate() override
Compute the digest, based on the provided data.
Definition md5.cpp:17
void add(const uint8_t *data, size_t len) override
Add bytes of data for the digest.
Definition md5.cpp:15
void init() override
Initialize a new MD5 digest computation.
Definition md5.cpp:10
StateCallbackManager state_callback_
Definition ota_backend.h:92
void register_ota_platform(OTAComponent *ota_caller)
std::unique_ptr< ota::OTABackend > make_ota_backend()
@ OTA_RESPONSE_ERROR_MD5_MISMATCH
Definition ota_backend.h:39
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT yield()
Definition core.cpp:29
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:31
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:30
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t length
Definition tt21100.cpp:0