ESPHome 2025.6.3
Loading...
Searching...
No Matches
ota_esphome.cpp
Go to the documentation of this file.
1#include "ota_esphome.h"
2#ifdef USE_OTA
12#include "esphome/core/hal.h"
13#include "esphome/core/log.h"
14#include "esphome/core/util.h"
15
16#include <cerrno>
17#include <cstdio>
18
19namespace esphome {
20
21static const char *const TAG = "esphome.ota";
22static constexpr u_int16_t OTA_BLOCK_SIZE = 8192;
23
25#ifdef USE_OTA_STATE_CALLBACK
27#endif
28
29 server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
30 if (server_ == nullptr) {
31 ESP_LOGW(TAG, "Could not create socket");
32 this->mark_failed();
33 return;
34 }
35 int enable = 1;
36 int err = server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
37 if (err != 0) {
38 ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
39 // we can still continue
40 }
41 err = server_->setblocking(false);
42 if (err != 0) {
43 ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
44 this->mark_failed();
45 return;
46 }
47
48 struct sockaddr_storage server;
49
50 socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
51 if (sl == 0) {
52 ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
53 this->mark_failed();
54 return;
55 }
56
57 err = server_->bind((struct sockaddr *) &server, sizeof(server));
58 if (err != 0) {
59 ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
60 this->mark_failed();
61 return;
62 }
63
64 err = server_->listen(4);
65 if (err != 0) {
66 ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
67 this->mark_failed();
68 return;
69 }
70}
71
73 ESP_LOGCONFIG(TAG,
74 "Over-The-Air updates:\n"
75 " Address: %s:%u\n"
76 " Version: %d",
77 network::get_use_address().c_str(), this->port_, USE_OTA_VERSION);
78#ifdef USE_OTA_PASSWORD
79 if (!this->password_.empty()) {
80 ESP_LOGCONFIG(TAG, " Password configured");
81 }
82#endif
83}
84
86
87static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
88
91 bool update_started = false;
92 size_t total = 0;
93 uint32_t last_progress = 0;
94 uint8_t buf[1024];
95 char *sbuf = reinterpret_cast<char *>(buf);
96 size_t ota_size;
97 uint8_t ota_features;
98 std::unique_ptr<ota::OTABackend> backend;
99 (void) ota_features;
100#if USE_OTA_VERSION == 2
101 size_t size_acknowledged = 0;
102#endif
103
104 if (client_ == nullptr) {
105 // Check if the server socket is ready before accepting
106 if (this->server_->ready()) {
107 struct sockaddr_storage source_addr;
108 socklen_t addr_len = sizeof(source_addr);
109 client_ = server_->accept((struct sockaddr *) &source_addr, &addr_len);
110 }
111 }
112 if (client_ == nullptr)
113 return;
114
115 int enable = 1;
116 int err = client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
117 if (err != 0) {
118 ESP_LOGW(TAG, "Socket could not enable TCP nodelay, errno %d", errno);
119 client_->close();
120 client_ = nullptr;
121 return;
122 }
123
124 ESP_LOGD(TAG, "Starting update from %s", this->client_->getpeername().c_str());
125 this->status_set_warning();
126#ifdef USE_OTA_STATE_CALLBACK
127 this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
128#endif
129
130 if (!this->readall_(buf, 5)) {
131 ESP_LOGW(TAG, "Reading magic bytes failed");
132 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
133 }
134 // 0x6C, 0x26, 0xF7, 0x5C, 0x45
135 if (buf[0] != 0x6C || buf[1] != 0x26 || buf[2] != 0xF7 || buf[3] != 0x5C || buf[4] != 0x45) {
136 ESP_LOGW(TAG, "Magic bytes do not match! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3],
137 buf[4]);
139 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
140 }
141
142 // Send OK and version - 2 bytes
143 buf[0] = ota::OTA_RESPONSE_OK;
144 buf[1] = USE_OTA_VERSION;
145 this->writeall_(buf, 2);
146
147 backend = ota::make_ota_backend();
148
149 // Read features - 1 byte
150 if (!this->readall_(buf, 1)) {
151 ESP_LOGW(TAG, "Reading features failed");
152 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
153 }
154 ota_features = buf[0]; // NOLINT
155 ESP_LOGV(TAG, "Features: 0x%02X", ota_features);
156
157 // Acknowledge header - 1 byte
159 if ((ota_features & FEATURE_SUPPORTS_COMPRESSION) != 0 && backend->supports_compression()) {
161 }
162
163 this->writeall_(buf, 1);
164
165#ifdef USE_OTA_PASSWORD
166 if (!this->password_.empty()) {
168 this->writeall_(buf, 1);
169 md5::MD5Digest md5{};
170 md5.init();
171 sprintf(sbuf, "%08" PRIx32, random_uint32());
172 md5.add(sbuf, 8);
173 md5.calculate();
174 md5.get_hex(sbuf);
175 ESP_LOGV(TAG, "Auth: Nonce is %s", sbuf);
176
177 // Send nonce, 32 bytes hex MD5
178 if (!this->writeall_(reinterpret_cast<uint8_t *>(sbuf), 32)) {
179 ESP_LOGW(TAG, "Auth: Writing nonce failed");
180 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
181 }
182
183 // prepare challenge
184 md5.init();
185 md5.add(this->password_.c_str(), this->password_.length());
186 // add nonce
187 md5.add(sbuf, 32);
188
189 // Receive cnonce, 32 bytes hex MD5
190 if (!this->readall_(buf, 32)) {
191 ESP_LOGW(TAG, "Auth: Reading cnonce failed");
192 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
193 }
194 sbuf[32] = '\0';
195 ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf);
196 // add cnonce
197 md5.add(sbuf, 32);
198
199 // calculate result
200 md5.calculate();
201 md5.get_hex(sbuf);
202 ESP_LOGV(TAG, "Auth: Result is %s", sbuf);
203
204 // Receive result, 32 bytes hex MD5
205 if (!this->readall_(buf + 64, 32)) {
206 ESP_LOGW(TAG, "Auth: Reading response failed");
207 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
208 }
209 sbuf[64 + 32] = '\0';
210 ESP_LOGV(TAG, "Auth: Response is %s", sbuf + 64);
211
212 bool matches = true;
213 for (uint8_t i = 0; i < 32; i++)
214 matches = matches && buf[i] == buf[64 + i];
215
216 if (!matches) {
217 ESP_LOGW(TAG, "Auth failed! Passwords do not match");
219 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
220 }
221 }
222#endif // USE_OTA_PASSWORD
223
224 // Acknowledge auth OK - 1 byte
226 this->writeall_(buf, 1);
227
228 // Read size, 4 bytes MSB first
229 if (!this->readall_(buf, 4)) {
230 ESP_LOGW(TAG, "Reading size failed");
231 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
232 }
233 ota_size = 0;
234 for (uint8_t i = 0; i < 4; i++) {
235 ota_size <<= 8;
236 ota_size |= buf[i];
237 }
238 ESP_LOGV(TAG, "Size is %u bytes", ota_size);
239
240 error_code = backend->begin(ota_size);
241 if (error_code != ota::OTA_RESPONSE_OK)
242 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
243 update_started = true;
244
245 // Acknowledge prepare OK - 1 byte
247 this->writeall_(buf, 1);
248
249 // Read binary MD5, 32 bytes
250 if (!this->readall_(buf, 32)) {
251 ESP_LOGW(TAG, "Reading binary MD5 checksum failed");
252 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
253 }
254 sbuf[32] = '\0';
255 ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf);
256 backend->set_update_md5(sbuf);
257
258 // Acknowledge MD5 OK - 1 byte
260 this->writeall_(buf, 1);
261
262 while (total < ota_size) {
263 // TODO: timeout check
264 size_t requested = std::min(sizeof(buf), ota_size - total);
265 ssize_t read = this->client_->read(buf, requested);
266 if (read == -1) {
267 if (errno == EAGAIN || errno == EWOULDBLOCK) {
268 App.feed_wdt();
269 delay(1);
270 continue;
271 }
272 ESP_LOGW(TAG, "Error receiving data for update, errno %d", errno);
273 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
274 } else if (read == 0) {
275 // $ man recv
276 // "When a stream socket peer has performed an orderly shutdown, the return value will
277 // be 0 (the traditional "end-of-file" return)."
278 ESP_LOGW(TAG, "Remote end closed connection");
279 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
280 }
281
282 error_code = backend->write(buf, read);
283 if (error_code != ota::OTA_RESPONSE_OK) {
284 ESP_LOGW(TAG, "Error writing binary data to flash!, error_code: %d", error_code);
285 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
286 }
287 total += read;
288#if USE_OTA_VERSION == 2
289 while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) {
291 this->writeall_(buf, 1);
292 size_acknowledged += OTA_BLOCK_SIZE;
293 }
294#endif
295
296 uint32_t now = millis();
297 if (now - last_progress > 1000) {
298 last_progress = now;
299 float percentage = (total * 100.0f) / ota_size;
300 ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
301#ifdef USE_OTA_STATE_CALLBACK
302 this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
303#endif
304 // feed watchdog and give other tasks a chance to run
305 App.feed_wdt();
306 yield();
307 }
308 }
309
310 // Acknowledge receive OK - 1 byte
312 this->writeall_(buf, 1);
313
314 error_code = backend->end();
315 if (error_code != ota::OTA_RESPONSE_OK) {
316 ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
317 goto error; // NOLINT(cppcoreguidelines-avoid-goto)
318 }
319
320 // Acknowledge Update end OK - 1 byte
322 this->writeall_(buf, 1);
323
324 // Read ACK
325 if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
326 ESP_LOGW(TAG, "Reading back acknowledgement failed");
327 // do not go to error, this is not fatal
328 }
329
330 this->client_->close();
331 this->client_ = nullptr;
332 delay(10);
333 ESP_LOGI(TAG, "Update complete");
334 this->status_clear_warning();
335#ifdef USE_OTA_STATE_CALLBACK
336 this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, 0);
337#endif
338 delay(100); // NOLINT
340
341error:
342 buf[0] = static_cast<uint8_t>(error_code);
343 this->writeall_(buf, 1);
344 this->client_->close();
345 this->client_ = nullptr;
346
347 if (backend != nullptr && update_started) {
348 backend->abort();
349 }
350
351 this->status_momentary_error("onerror", 5000);
352#ifdef USE_OTA_STATE_CALLBACK
353 this->state_callback_.call(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
354#endif
355}
356
357bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
358 uint32_t start = millis();
359 uint32_t at = 0;
360 while (len - at > 0) {
361 uint32_t now = millis();
362 if (now - start > 1000) {
363 ESP_LOGW(TAG, "Timed out reading %d bytes of data", len);
364 return false;
365 }
366
367 ssize_t read = this->client_->read(buf + at, len - at);
368 if (read == -1) {
369 if (errno == EAGAIN || errno == EWOULDBLOCK) {
370 App.feed_wdt();
371 delay(1);
372 continue;
373 }
374 ESP_LOGW(TAG, "Failed to read %d bytes of data, errno %d", len, errno);
375 return false;
376 } else if (read == 0) {
377 ESP_LOGW(TAG, "Remote closed connection");
378 return false;
379 } else {
380 at += read;
381 }
382 App.feed_wdt();
383 delay(1);
384 }
385
386 return true;
387}
388bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
389 uint32_t start = millis();
390 uint32_t at = 0;
391 while (len - at > 0) {
392 uint32_t now = millis();
393 if (now - start > 1000) {
394 ESP_LOGW(TAG, "Timed out writing %d bytes of data", len);
395 return false;
396 }
397
398 ssize_t written = this->client_->write(buf + at, len - at);
399 if (written == -1) {
400 if (errno == EAGAIN || errno == EWOULDBLOCK) {
401 App.feed_wdt();
402 delay(1);
403 continue;
404 }
405 ESP_LOGW(TAG, "Failed to write %d bytes of data, errno %d", len, errno);
406 return false;
407 } else {
408 at += written;
409 }
410 App.feed_wdt();
411 delay(1);
412 }
413 return true;
414}
415
417uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
418void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
419} // namespace esphome
420#endif
void feed_wdt(uint32_t time=0)
virtual void mark_failed()
Mark this component as failed.
void status_momentary_error(const std::string &name, uint32_t length=5000)
void status_set_warning(const char *message="unspecified")
void status_clear_warning()
bool writeall_(const uint8_t *buf, size_t len)
float get_setup_priority() const override
bool readall_(uint8_t *buf, size_t len)
void set_port(uint16_t port)
Manually set the port OTA should listen on.
std::unique_ptr< socket::Socket > server_
Definition ota_esphome.h:40
std::unique_ptr< socket::Socket > client_
Definition ota_esphome.h:41
void init()
Initialize a new MD5 digest computation.
Definition md5.cpp:11
CallbackManager< void(ota::OTAState, float, uint8_t)> state_callback_
Definition ota_backend.h:70
uint32_t socklen_t
Definition headers.h:97
__int64 ssize_t
Definition httplib.h:175
std::string get_use_address()
Get the active network hostname.
Definition util.cpp:65
void register_ota_platform(OTAComponent *ota_caller)
std::unique_ptr< ota::OTABackend > make_ota_backend()
@ OTA_RESPONSE_UPDATE_PREPARE_OK
Definition ota_backend.h:20
@ OTA_RESPONSE_SUPPORTS_COMPRESSION
Definition ota_backend.h:24
@ OTA_RESPONSE_BIN_MD5_OK
Definition ota_backend.h:21
@ OTA_RESPONSE_UPDATE_END_OK
Definition ota_backend.h:23
@ OTA_RESPONSE_RECEIVE_OK
Definition ota_backend.h:22
@ OTA_RESPONSE_CHUNK_OK
Definition ota_backend.h:25
@ OTA_RESPONSE_ERROR_AUTH_INVALID
Definition ota_backend.h:29
@ OTA_RESPONSE_ERROR_UNKNOWN
Definition ota_backend.h:40
@ OTA_RESPONSE_ERROR_MAGIC
Definition ota_backend.h:27
@ OTA_RESPONSE_HEADER_OK
Definition ota_backend.h:18
@ OTA_RESPONSE_REQUEST_AUTH
Definition ota_backend.h:16
const float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.cpp:27
std::unique_ptr< Socket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.cpp:44
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port)
Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
Definition socket.cpp:82
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:302
void IRAM_ATTR HOT yield()
Definition core.cpp:27
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition helpers.cpp:196
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.