ESPHome  2023.5.5
ota_component.cpp
Go to the documentation of this file.
1 #include "ota_component.h"
2 #include "ota_backend.h"
6 #include "ota_backend_esp_idf.h"
7 
8 #include "esphome/core/log.h"
10 #include "esphome/core/hal.h"
11 #include "esphome/core/util.h"
14 
15 #include <cerrno>
16 #include <cstdio>
17 
18 namespace esphome {
19 namespace ota {
20 
21 static const char *const TAG = "ota";
22 
23 static const uint8_t OTA_VERSION_1_0 = 1;
24 
25 OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
26 
27 std::unique_ptr<OTABackend> make_ota_backend() {
28 #ifdef USE_ARDUINO
29 #ifdef USE_ESP8266
30  return make_unique<ArduinoESP8266OTABackend>();
31 #endif // USE_ESP8266
32 #ifdef USE_ESP32
33  return make_unique<ArduinoESP32OTABackend>();
34 #endif // USE_ESP32
35 #endif // USE_ARDUINO
36 #ifdef USE_ESP_IDF
37  return make_unique<IDFOTABackend>();
38 #endif // USE_ESP_IDF
39 #ifdef USE_RP2040
40  return make_unique<ArduinoRP2040OTABackend>();
41 #endif // USE_RP2040
42 }
43 
44 OTAComponent::OTAComponent() { global_ota_component = this; }
45 
47  server_ = socket::socket_ip(SOCK_STREAM, 0);
48  if (server_ == nullptr) {
49  ESP_LOGW(TAG, "Could not create socket.");
50  this->mark_failed();
51  return;
52  }
53  int enable = 1;
54  int err = server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
55  if (err != 0) {
56  ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
57  // we can still continue
58  }
59  err = server_->setblocking(false);
60  if (err != 0) {
61  ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
62  this->mark_failed();
63  return;
64  }
65 
66  struct sockaddr_storage server;
67 
68  socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
69  if (sl == 0) {
70  ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
71  this->mark_failed();
72  return;
73  }
74 
75  err = server_->bind((struct sockaddr *) &server, sizeof(server));
76  if (err != 0) {
77  ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
78  this->mark_failed();
79  return;
80  }
81 
82  err = server_->listen(4);
83  if (err != 0) {
84  ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
85  this->mark_failed();
86  return;
87  }
88 
89  this->dump_config();
90 }
91 
93  ESP_LOGCONFIG(TAG, "Over-The-Air Updates:");
94  ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
95 #ifdef USE_OTA_PASSWORD
96  if (!this->password_.empty()) {
97  ESP_LOGCONFIG(TAG, " Using Password.");
98  }
99 #endif
100  if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
102  ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %d restarts",
104  }
105 }
106 
108  this->handle_();
109 
110  if (this->has_safe_mode_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_enable_time_) {
111  this->has_safe_mode_ = false;
112  // successful boot, reset counter
113  ESP_LOGI(TAG, "Boot seems successful, resetting boot loop counter.");
114  this->clean_rtc();
115  }
116 }
117 
118 static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
119 
122  bool update_started = false;
123  size_t total = 0;
124  uint32_t last_progress = 0;
125  uint8_t buf[1024];
126  char *sbuf = reinterpret_cast<char *>(buf);
127  size_t ota_size;
128  uint8_t ota_features;
129  std::unique_ptr<OTABackend> backend;
130  (void) ota_features;
131 
132  if (client_ == nullptr) {
133  struct sockaddr_storage source_addr;
134  socklen_t addr_len = sizeof(source_addr);
135  client_ = server_->accept((struct sockaddr *) &source_addr, &addr_len);
136  }
137  if (client_ == nullptr)
138  return;
139 
140  int enable = 1;
141  int err = client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
142  if (err != 0) {
143  ESP_LOGW(TAG, "Socket could not enable tcp nodelay, errno: %d", errno);
144  return;
145  }
146 
147  ESP_LOGD(TAG, "Starting OTA Update from %s...", this->client_->getpeername().c_str());
148  this->status_set_warning();
149 #ifdef USE_OTA_STATE_CALLBACK
150  this->state_callback_.call(OTA_STARTED, 0.0f, 0);
151 #endif
152 
153  if (!this->readall_(buf, 5)) {
154  ESP_LOGW(TAG, "Reading magic bytes failed!");
155  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
156  }
157  // 0x6C, 0x26, 0xF7, 0x5C, 0x45
158  if (buf[0] != 0x6C || buf[1] != 0x26 || buf[2] != 0xF7 || buf[3] != 0x5C || buf[4] != 0x45) {
159  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],
160  buf[4]);
161  error_code = OTA_RESPONSE_ERROR_MAGIC;
162  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
163  }
164 
165  // Send OK and version - 2 bytes
166  buf[0] = OTA_RESPONSE_OK;
167  buf[1] = OTA_VERSION_1_0;
168  this->writeall_(buf, 2);
169 
170  backend = make_ota_backend();
171 
172  // Read features - 1 byte
173  if (!this->readall_(buf, 1)) {
174  ESP_LOGW(TAG, "Reading features failed!");
175  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
176  }
177  ota_features = buf[0]; // NOLINT
178  ESP_LOGV(TAG, "OTA features is 0x%02X", ota_features);
179 
180  // Acknowledge header - 1 byte
181  buf[0] = OTA_RESPONSE_HEADER_OK;
182  if ((ota_features & FEATURE_SUPPORTS_COMPRESSION) != 0 && backend->supports_compression()) {
184  }
185 
186  this->writeall_(buf, 1);
187 
188 #ifdef USE_OTA_PASSWORD
189  if (!this->password_.empty()) {
190  buf[0] = OTA_RESPONSE_REQUEST_AUTH;
191  this->writeall_(buf, 1);
192  md5::MD5Digest md5{};
193  md5.init();
194  sprintf(sbuf, "%08X", random_uint32());
195  md5.add(sbuf, 8);
196  md5.calculate();
197  md5.get_hex(sbuf);
198  ESP_LOGV(TAG, "Auth: Nonce is %s", sbuf);
199 
200  // Send nonce, 32 bytes hex MD5
201  if (!this->writeall_(reinterpret_cast<uint8_t *>(sbuf), 32)) {
202  ESP_LOGW(TAG, "Auth: Writing nonce failed!");
203  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
204  }
205 
206  // prepare challenge
207  md5.init();
208  md5.add(this->password_.c_str(), this->password_.length());
209  // add nonce
210  md5.add(sbuf, 32);
211 
212  // Receive cnonce, 32 bytes hex MD5
213  if (!this->readall_(buf, 32)) {
214  ESP_LOGW(TAG, "Auth: Reading cnonce failed!");
215  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
216  }
217  sbuf[32] = '\0';
218  ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf);
219  // add cnonce
220  md5.add(sbuf, 32);
221 
222  // calculate result
223  md5.calculate();
224  md5.get_hex(sbuf);
225  ESP_LOGV(TAG, "Auth: Result is %s", sbuf);
226 
227  // Receive result, 32 bytes hex MD5
228  if (!this->readall_(buf + 64, 32)) {
229  ESP_LOGW(TAG, "Auth: Reading response failed!");
230  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
231  }
232  sbuf[64 + 32] = '\0';
233  ESP_LOGV(TAG, "Auth: Response is %s", sbuf + 64);
234 
235  bool matches = true;
236  for (uint8_t i = 0; i < 32; i++)
237  matches = matches && buf[i] == buf[64 + i];
238 
239  if (!matches) {
240  ESP_LOGW(TAG, "Auth failed! Passwords do not match!");
241  error_code = OTA_RESPONSE_ERROR_AUTH_INVALID;
242  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
243  }
244  }
245 #endif // USE_OTA_PASSWORD
246 
247  // Acknowledge auth OK - 1 byte
248  buf[0] = OTA_RESPONSE_AUTH_OK;
249  this->writeall_(buf, 1);
250 
251  // Read size, 4 bytes MSB first
252  if (!this->readall_(buf, 4)) {
253  ESP_LOGW(TAG, "Reading size failed!");
254  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
255  }
256  ota_size = 0;
257  for (uint8_t i = 0; i < 4; i++) {
258  ota_size <<= 8;
259  ota_size |= buf[i];
260  }
261  ESP_LOGV(TAG, "OTA size is %u bytes", ota_size);
262 
263  error_code = backend->begin(ota_size);
264  if (error_code != OTA_RESPONSE_OK)
265  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
266  update_started = true;
267 
268  // Acknowledge prepare OK - 1 byte
270  this->writeall_(buf, 1);
271 
272  // Read binary MD5, 32 bytes
273  if (!this->readall_(buf, 32)) {
274  ESP_LOGW(TAG, "Reading binary MD5 checksum failed!");
275  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
276  }
277  sbuf[32] = '\0';
278  ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf);
279  backend->set_update_md5(sbuf);
280 
281  // Acknowledge MD5 OK - 1 byte
282  buf[0] = OTA_RESPONSE_BIN_MD5_OK;
283  this->writeall_(buf, 1);
284 
285  while (total < ota_size) {
286  // TODO: timeout check
287  size_t requested = std::min(sizeof(buf), ota_size - total);
288  ssize_t read = this->client_->read(buf, requested);
289  if (read == -1) {
290  if (errno == EAGAIN || errno == EWOULDBLOCK) {
291  App.feed_wdt();
292  delay(1);
293  continue;
294  }
295  ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno);
296  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
297  } else if (read == 0) {
298  // $ man recv
299  // "When a stream socket peer has performed an orderly shutdown, the return value will
300  // be 0 (the traditional "end-of-file" return)."
301  ESP_LOGW(TAG, "Remote end closed connection");
302  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
303  }
304 
305  error_code = backend->write(buf, read);
306  if (error_code != OTA_RESPONSE_OK) {
307  ESP_LOGW(TAG, "Error writing binary data to flash!, error_code: %d", error_code);
308  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
309  }
310  total += read;
311 
312  uint32_t now = millis();
313  if (now - last_progress > 1000) {
314  last_progress = now;
315  float percentage = (total * 100.0f) / ota_size;
316  ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
317 #ifdef USE_OTA_STATE_CALLBACK
318  this->state_callback_.call(OTA_IN_PROGRESS, percentage, 0);
319 #endif
320  // feed watchdog and give other tasks a chance to run
321  App.feed_wdt();
322  yield();
323  }
324  }
325 
326  // Acknowledge receive OK - 1 byte
327  buf[0] = OTA_RESPONSE_RECEIVE_OK;
328  this->writeall_(buf, 1);
329 
330  error_code = backend->end();
331  if (error_code != OTA_RESPONSE_OK) {
332  ESP_LOGW(TAG, "Error ending OTA!, error_code: %d", error_code);
333  goto error; // NOLINT(cppcoreguidelines-avoid-goto)
334  }
335 
336  // Acknowledge Update end OK - 1 byte
338  this->writeall_(buf, 1);
339 
340  // Read ACK
341  if (!this->readall_(buf, 1) || buf[0] != OTA_RESPONSE_OK) {
342  ESP_LOGW(TAG, "Reading back acknowledgement failed!");
343  // do not go to error, this is not fatal
344  }
345 
346  this->client_->close();
347  this->client_ = nullptr;
348  delay(10);
349  ESP_LOGI(TAG, "OTA update finished!");
350  this->status_clear_warning();
351 #ifdef USE_OTA_STATE_CALLBACK
352  this->state_callback_.call(OTA_COMPLETED, 100.0f, 0);
353 #endif
354  delay(100); // NOLINT
355  App.safe_reboot();
356 
357 error:
358  buf[0] = static_cast<uint8_t>(error_code);
359  this->writeall_(buf, 1);
360  this->client_->close();
361  this->client_ = nullptr;
362 
363  if (backend != nullptr && update_started) {
364  backend->abort();
365  }
366 
367  this->status_momentary_error("onerror", 5000);
368 #ifdef USE_OTA_STATE_CALLBACK
369  this->state_callback_.call(OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
370 #endif
371 }
372 
373 bool OTAComponent::readall_(uint8_t *buf, size_t len) {
374  uint32_t start = millis();
375  uint32_t at = 0;
376  while (len - at > 0) {
377  uint32_t now = millis();
378  if (now - start > 1000) {
379  ESP_LOGW(TAG, "Timed out reading %d bytes of data", len);
380  return false;
381  }
382 
383  ssize_t read = this->client_->read(buf + at, len - at);
384  if (read == -1) {
385  if (errno == EAGAIN || errno == EWOULDBLOCK) {
386  App.feed_wdt();
387  delay(1);
388  continue;
389  }
390  ESP_LOGW(TAG, "Failed to read %d bytes of data, errno: %d", len, errno);
391  return false;
392  } else if (read == 0) {
393  ESP_LOGW(TAG, "Remote closed connection");
394  return false;
395  } else {
396  at += read;
397  }
398  App.feed_wdt();
399  delay(1);
400  }
401 
402  return true;
403 }
404 bool OTAComponent::writeall_(const uint8_t *buf, size_t len) {
405  uint32_t start = millis();
406  uint32_t at = 0;
407  while (len - at > 0) {
408  uint32_t now = millis();
409  if (now - start > 1000) {
410  ESP_LOGW(TAG, "Timed out writing %d bytes of data", len);
411  return false;
412  }
413 
414  ssize_t written = this->client_->write(buf + at, len - at);
415  if (written == -1) {
416  if (errno == EAGAIN || errno == EWOULDBLOCK) {
417  App.feed_wdt();
418  delay(1);
419  continue;
420  }
421  ESP_LOGW(TAG, "Failed to write %d bytes of data, errno: %d", len, errno);
422  return false;
423  } else {
424  at += written;
425  }
426  App.feed_wdt();
427  delay(1);
428  }
429  return true;
430 }
431 
433 uint16_t OTAComponent::get_port() const { return this->port_; }
434 void OTAComponent::set_port(uint16_t port) { this->port_ = port; }
435 
436 void OTAComponent::set_safe_mode_pending(const bool &pending) {
437  if (!this->has_safe_mode_)
438  return;
439 
440  uint32_t current_rtc = this->read_rtc_();
441 
442  if (pending && current_rtc != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) {
443  ESP_LOGI(TAG, "Device will enter safe mode on next boot.");
445  }
446 
447  if (!pending && current_rtc == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) {
448  ESP_LOGI(TAG, "Safe mode pending has been cleared");
449  this->clean_rtc();
450  }
451 }
454 }
455 
456 bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) {
457  this->has_safe_mode_ = true;
458  this->safe_mode_start_time_ = millis();
459  this->safe_mode_enable_time_ = enable_time;
460  this->safe_mode_num_attempts_ = num_attempts;
461  this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
462  this->safe_mode_rtc_value_ = this->read_rtc_();
463 
464  bool is_manual_safe_mode = this->safe_mode_rtc_value_ == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC;
465 
466  if (is_manual_safe_mode) {
467  ESP_LOGI(TAG, "Safe mode has been entered manually");
468  } else {
469  ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_);
470  }
471 
472  if (this->safe_mode_rtc_value_ >= num_attempts || is_manual_safe_mode) {
473  this->clean_rtc();
474 
475  if (!is_manual_safe_mode) {
476  ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode.");
477  }
478 
479  this->status_set_error();
480  this->set_timeout(enable_time, []() {
481  ESP_LOGE(TAG, "No OTA attempt made, restarting.");
482  App.reboot();
483  });
484 
485  // Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised.
486  delay(300); // NOLINT
487  App.setup();
488 
489  ESP_LOGI(TAG, "Waiting for OTA attempt.");
490 
491  return true;
492  } else {
493  // increment counter
494  this->write_rtc_(this->safe_mode_rtc_value_ + 1);
495  return false;
496  }
497 }
499  this->rtc_.save(&val);
501 }
503  uint32_t val;
504  if (!this->rtc_.load(&val))
505  return 0;
506  return val;
507 }
511  this->clean_rtc();
512 }
513 
514 #ifdef USE_OTA_STATE_CALLBACK
515 void OTAComponent::add_on_state_callback(std::function<void(OTAState, float, uint8_t)> &&callback) {
516  this->state_callback_.add(std::move(callback));
517 }
518 #endif
519 
520 } // namespace ota
521 } // namespace esphome
void init()
Initialize a new MD5 digest computation.
Definition: md5.cpp:10
std::unique_ptr< Socket > socket_ip(int type, int protocol)
Create a socket in the newest available IP domain (IPv6 or IPv4) of the given type and protocol...
Definition: socket.cpp:12
uint32_t safe_mode_enable_time_
The time safe mode should be on for.
Definition: ota_component.h:95
const float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition: component.cpp:25
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:53
std::string get_use_address()
Get the active network hostname.
Definition: util.cpp:44
ESPPreferenceObject rtc_
Definition: ota_component.h:98
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition: helpers.cpp:103
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition: component.cpp:68
void add_on_state_callback(std::function< void(OTAState, float, uint8_t)> &&callback)
uint32_t socklen_t
Definition: headers.h:97
mopeka_std_values val[4]
void setup()
Set up all the registered components. Call this at the end of your setup() function.
Definition: application.cpp:28
uint32_t safe_mode_start_time_
stores when safe mode was enabled.
Definition: ota_component.h:94
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:27
void status_momentary_error(const std::string &name, uint32_t length=5000)
Definition: component.cpp:159
bool readall_(uint8_t *buf, size_t len)
bool save(const T *src)
Definition: preferences.h:21
static const uint32_t ENTER_SAFE_MODE_MAGIC
a magic number to indicate that safe mode should be entered on next boot
void set_port(uint16_t port)
Manually set the port OTA should listen on.
ESPPreferences * global_preferences
void status_clear_warning()
Definition: component.cpp:153
std::unique_ptr< socket::Socket > client_
Definition: ota_component.h:91
float get_setup_priority() const override
bool has_safe_mode_
stores whether safe mode can be enabled.
Definition: ota_component.h:93
std::unique_ptr< OTABackend > make_ota_backend()
void write_rtc_(uint32_t val)
Application App
Global storage of Application pointer - only one Application can exist.
void on_safe_shutdown() override
void status_set_warning()
Definition: component.cpp:145
OTAComponent * global_ota_component
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time)
std::string size_t len
Definition: helpers.h:286
void IRAM_ATTR HOT yield()
Definition: core.cpp:26
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:112
Definition: a4988.cpp:4
virtual bool sync()=0
Commit pending writes to flash.
void set_safe_mode_pending(const bool &pending)
Set to true if the next startup will enter safe mode.
bool writeall_(const uint8_t *buf, size_t len)
std::unique_ptr< socket::Socket > server_
Definition: ota_component.h:90
CallbackManager< void(OTAState, float, uint8_t)> state_callback_
OTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA...
Definition: ota_component.h:43
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:28