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