mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 06:33:51 +00:00
Replace custom OTA implementation in web_server_base (#9274)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
@@ -67,7 +67,28 @@ class OTAComponent : public Component {
|
||||
}
|
||||
|
||||
protected:
|
||||
CallbackManager<void(ota::OTAState, float, uint8_t)> state_callback_{};
|
||||
/** Extended callback manager with deferred call support.
|
||||
*
|
||||
* This adds a call_deferred() method for thread-safe execution from other tasks.
|
||||
*/
|
||||
class StateCallbackManager : public CallbackManager<void(OTAState, float, uint8_t)> {
|
||||
public:
|
||||
StateCallbackManager(OTAComponent *component) : component_(component) {}
|
||||
|
||||
/** Call callbacks with deferral to main loop (for thread safety).
|
||||
*
|
||||
* This should be used by OTA implementations that run in separate tasks
|
||||
* (like web_server OTA) to ensure callbacks execute in the main loop.
|
||||
*/
|
||||
void call_deferred(ota::OTAState state, float progress, uint8_t error) {
|
||||
component_->defer([this, state, progress, error]() { this->call(state, progress, error); });
|
||||
}
|
||||
|
||||
private:
|
||||
OTAComponent *component_;
|
||||
};
|
||||
|
||||
StateCallbackManager state_callback_{this};
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -89,6 +110,11 @@ class OTAGlobalCallback {
|
||||
|
||||
OTAGlobalCallback *get_global_ota_callback();
|
||||
void register_ota_platform(OTAComponent *ota_caller);
|
||||
|
||||
// OTA implementations should use:
|
||||
// - state_callback_.call() when already in main loop (e.g., esphome OTA)
|
||||
// - state_callback_.call_deferred() when in separate task (e.g., web_server OTA)
|
||||
// This ensures proper callback execution in all contexts.
|
||||
#endif
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend();
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@ static const char *const TAG = "ota.arduino_esp32";
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP32OTABackend>(); }
|
||||
|
||||
OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) {
|
||||
// Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA
|
||||
// where the exact firmware size is unknown due to multipart encoding
|
||||
if (image_size == 0) {
|
||||
image_size = UPDATE_SIZE_UNKNOWN;
|
||||
}
|
||||
bool ret = Update.begin(image_size, U_FLASH);
|
||||
if (ret) {
|
||||
return OTA_RESPONSE_OK;
|
||||
@@ -29,7 +34,10 @@ OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) {
|
||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
void ArduinoESP32OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
||||
void ArduinoESP32OTABackend::set_update_md5(const char *md5) {
|
||||
Update.setMD5(md5);
|
||||
this->md5_set_ = true;
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) {
|
||||
size_t written = Update.write(data, len);
|
||||
@@ -44,7 +52,9 @@ OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoESP32OTABackend::end() {
|
||||
if (Update.end()) {
|
||||
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
|
||||
// This matches the behavior of the old web_server OTA implementation
|
||||
if (Update.end(!this->md5_set_)) {
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ class ArduinoESP32OTABackend : public OTABackend {
|
||||
OTAResponseTypes end() override;
|
||||
void abort() override;
|
||||
bool supports_compression() override { return false; }
|
||||
|
||||
private:
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota
|
||||
|
||||
@@ -17,6 +17,11 @@ static const char *const TAG = "ota.arduino_esp8266";
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP8266OTABackend>(); }
|
||||
|
||||
OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
|
||||
// Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space
|
||||
if (image_size == 0) {
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
image_size = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
|
||||
}
|
||||
bool ret = Update.begin(image_size, U_FLASH);
|
||||
if (ret) {
|
||||
esp8266::preferences_prevent_write(true);
|
||||
@@ -38,7 +43,10 @@ OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
|
||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
||||
void ArduinoESP8266OTABackend::set_update_md5(const char *md5) {
|
||||
Update.setMD5(md5);
|
||||
this->md5_set_ = true;
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) {
|
||||
size_t written = Update.write(data, len);
|
||||
@@ -53,13 +61,19 @@ OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoESP8266OTABackend::end() {
|
||||
if (Update.end()) {
|
||||
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
|
||||
// This matches the behavior of the old web_server OTA implementation
|
||||
bool success = Update.end(!this->md5_set_);
|
||||
|
||||
// On ESP8266, Update.end() might return false even with error code 0
|
||||
// Check the actual error code to determine success
|
||||
uint8_t error = Update.getError();
|
||||
|
||||
if (success || error == UPDATE_ERROR_OK) {
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
uint8_t error = Update.getError();
|
||||
ESP_LOGE(TAG, "End error: %d", error);
|
||||
|
||||
return OTA_RESPONSE_ERROR_UPDATE_END;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ class ArduinoESP8266OTABackend : public OTABackend {
|
||||
#else
|
||||
bool supports_compression() override { return false; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota
|
||||
|
||||
@@ -15,6 +15,11 @@ static const char *const TAG = "ota.arduino_libretiny";
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoLibreTinyOTABackend>(); }
|
||||
|
||||
OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
|
||||
// Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA
|
||||
// where the exact firmware size is unknown due to multipart encoding
|
||||
if (image_size == 0) {
|
||||
image_size = UPDATE_SIZE_UNKNOWN;
|
||||
}
|
||||
bool ret = Update.begin(image_size, U_FLASH);
|
||||
if (ret) {
|
||||
return OTA_RESPONSE_OK;
|
||||
@@ -29,7 +34,10 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
|
||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
||||
void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) {
|
||||
Update.setMD5(md5);
|
||||
this->md5_set_ = true;
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) {
|
||||
size_t written = Update.write(data, len);
|
||||
@@ -44,7 +52,9 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoLibreTinyOTABackend::end() {
|
||||
if (Update.end()) {
|
||||
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
|
||||
// This matches the behavior of the old web_server OTA implementation
|
||||
if (Update.end(!this->md5_set_)) {
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ class ArduinoLibreTinyOTABackend : public OTABackend {
|
||||
OTAResponseTypes end() override;
|
||||
void abort() override;
|
||||
bool supports_compression() override { return false; }
|
||||
|
||||
private:
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota
|
||||
|
||||
@@ -17,6 +17,8 @@ static const char *const TAG = "ota.arduino_rp2040";
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoRP2040OTABackend>(); }
|
||||
|
||||
OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
|
||||
// OTA size of 0 is not currently handled, but
|
||||
// web_server is not supported for RP2040, so this is not an issue.
|
||||
bool ret = Update.begin(image_size, U_FLASH);
|
||||
if (ret) {
|
||||
rp2040::preferences_prevent_write(true);
|
||||
@@ -38,7 +40,10 @@ OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
|
||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
||||
void ArduinoRP2040OTABackend::set_update_md5(const char *md5) {
|
||||
Update.setMD5(md5);
|
||||
this->md5_set_ = true;
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) {
|
||||
size_t written = Update.write(data, len);
|
||||
@@ -53,7 +58,9 @@ OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoRP2040OTABackend::end() {
|
||||
if (Update.end()) {
|
||||
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
|
||||
// This matches the behavior of the old web_server OTA implementation
|
||||
if (Update.end(!this->md5_set_)) {
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ class ArduinoRP2040OTABackend : public OTABackend {
|
||||
OTAResponseTypes end() override;
|
||||
void abort() override;
|
||||
bool supports_compression() override { return false; }
|
||||
|
||||
private:
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota
|
||||
|
||||
@@ -56,7 +56,10 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
void IDFOTABackend::set_update_md5(const char *expected_md5) { memcpy(this->expected_bin_md5_, expected_md5, 32); }
|
||||
void IDFOTABackend::set_update_md5(const char *expected_md5) {
|
||||
memcpy(this->expected_bin_md5_, expected_md5, 32);
|
||||
this->md5_set_ = true;
|
||||
}
|
||||
|
||||
OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) {
|
||||
esp_err_t err = esp_ota_write(this->update_handle_, data, len);
|
||||
@@ -73,10 +76,12 @@ OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
OTAResponseTypes IDFOTABackend::end() {
|
||||
this->md5_.calculate();
|
||||
if (!this->md5_.equals_hex(this->expected_bin_md5_)) {
|
||||
this->abort();
|
||||
return OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
if (this->md5_set_) {
|
||||
this->md5_.calculate();
|
||||
if (!this->md5_.equals_hex(this->expected_bin_md5_)) {
|
||||
this->abort();
|
||||
return OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
}
|
||||
}
|
||||
esp_err_t err = esp_ota_end(this->update_handle_);
|
||||
this->update_handle_ = 0;
|
||||
|
||||
@@ -24,6 +24,7 @@ class IDFOTABackend : public OTABackend {
|
||||
const esp_partition_t *partition_;
|
||||
md5::MD5Digest md5_{};
|
||||
char expected_bin_md5_[32];
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota
|
||||
|
||||
Reference in New Issue
Block a user