mirror of
https://github.com/esphome/esphome.git
synced 2025-01-18 20:10:55 +00:00
[http_request] Bugfix: run update function in a task (#8018)
This commit is contained in:
parent
16bf56b0f9
commit
8c6c45e6c1
@ -12,6 +12,8 @@
|
|||||||
#include "esp_crt_bundle.h"
|
#include "esp_crt_bundle.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace http_request {
|
namespace http_request {
|
||||||
|
|
||||||
@ -117,11 +119,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
App.feed_wdt();
|
container->feed_wdt();
|
||||||
container->content_length = esp_http_client_fetch_headers(client);
|
container->content_length = esp_http_client_fetch_headers(client);
|
||||||
App.feed_wdt();
|
container->feed_wdt();
|
||||||
container->status_code = esp_http_client_get_status_code(client);
|
container->status_code = esp_http_client_get_status_code(client);
|
||||||
App.feed_wdt();
|
container->feed_wdt();
|
||||||
if (is_success(container->status_code)) {
|
if (is_success(container->status_code)) {
|
||||||
container->duration_ms = millis() - start;
|
container->duration_ms = millis() - start;
|
||||||
return container;
|
return container;
|
||||||
@ -151,11 +153,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
App.feed_wdt();
|
container->feed_wdt();
|
||||||
container->content_length = esp_http_client_fetch_headers(client);
|
container->content_length = esp_http_client_fetch_headers(client);
|
||||||
App.feed_wdt();
|
container->feed_wdt();
|
||||||
container->status_code = esp_http_client_get_status_code(client);
|
container->status_code = esp_http_client_get_status_code(client);
|
||||||
App.feed_wdt();
|
container->feed_wdt();
|
||||||
if (is_success(container->status_code)) {
|
if (is_success(container->status_code)) {
|
||||||
container->duration_ms = millis() - start;
|
container->duration_ms = millis() - start;
|
||||||
return container;
|
return container;
|
||||||
@ -185,8 +187,9 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
App.feed_wdt();
|
this->feed_wdt();
|
||||||
int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
|
int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
|
||||||
|
this->feed_wdt();
|
||||||
this->bytes_read_ += read_len;
|
this->bytes_read_ += read_len;
|
||||||
|
|
||||||
this->duration_ms += (millis() - start);
|
this->duration_ms += (millis() - start);
|
||||||
@ -201,6 +204,13 @@ void HttpContainerIDF::end() {
|
|||||||
esp_http_client_cleanup(this->client_);
|
esp_http_client_cleanup(this->client_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HttpContainerIDF::feed_wdt() {
|
||||||
|
// Tests to see if the executing task has a watchdog timer attached
|
||||||
|
if (esp_task_wdt_status(nullptr) == ESP_OK) {
|
||||||
|
App.feed_wdt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace http_request
|
} // namespace http_request
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@ class HttpContainerIDF : public HttpContainer {
|
|||||||
int read(uint8_t *buf, size_t max_len) override;
|
int read(uint8_t *buf, size_t max_len) override;
|
||||||
void end() override;
|
void end() override;
|
||||||
|
|
||||||
|
/// @brief Feeds the watchdog timer if the executing task has one attached
|
||||||
|
void feed_wdt();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
esp_http_client_handle_t client_;
|
esp_http_client_handle_t client_;
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,13 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace http_request {
|
namespace http_request {
|
||||||
|
|
||||||
|
// The update function runs in a task only on ESP32s.
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#define UPDATE_RETURN vTaskDelete(nullptr) // Delete the current update task
|
||||||
|
#else
|
||||||
|
#define UPDATE_RETURN return
|
||||||
|
#endif
|
||||||
|
|
||||||
static const char *const TAG = "http_request.update";
|
static const char *const TAG = "http_request.update";
|
||||||
|
|
||||||
static const size_t MAX_READ_SIZE = 256;
|
static const size_t MAX_READ_SIZE = 256;
|
||||||
@ -29,113 +36,131 @@ void HttpRequestUpdate::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequestUpdate::update() {
|
void HttpRequestUpdate::update() {
|
||||||
auto container = this->request_parent_->get(this->source_url_);
|
#ifdef USE_ESP32
|
||||||
|
xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_);
|
||||||
|
#else
|
||||||
|
this->update_task(this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequestUpdate::update_task(void *params) {
|
||||||
|
HttpRequestUpdate *this_update = (HttpRequestUpdate *) params;
|
||||||
|
|
||||||
|
auto container = this_update->request_parent_->get(this_update->source_url_);
|
||||||
|
|
||||||
if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
|
if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
|
||||||
std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str());
|
std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str());
|
||||||
this->status_set_error(msg.c_str());
|
this_update->status_set_error(msg.c_str());
|
||||||
return;
|
UPDATE_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
uint8_t *data = allocator.allocate(container->content_length);
|
uint8_t *data = allocator.allocate(container->content_length);
|
||||||
if (data == nullptr) {
|
if (data == nullptr) {
|
||||||
std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
|
std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
|
||||||
this->status_set_error(msg.c_str());
|
this_update->status_set_error(msg.c_str());
|
||||||
container->end();
|
container->end();
|
||||||
return;
|
UPDATE_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t read_index = 0;
|
size_t read_index = 0;
|
||||||
while (container->get_bytes_read() < container->content_length) {
|
while (container->get_bytes_read() < container->content_length) {
|
||||||
int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
|
int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
|
||||||
|
|
||||||
App.feed_wdt();
|
|
||||||
yield();
|
yield();
|
||||||
|
|
||||||
read_index += read_bytes;
|
read_index += read_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string response((char *) data, read_index);
|
bool valid = false;
|
||||||
allocator.deallocate(data, container->content_length);
|
{ // Ensures the response string falls out of scope and deallocates before the task ends
|
||||||
|
std::string response((char *) data, read_index);
|
||||||
|
allocator.deallocate(data, container->content_length);
|
||||||
|
|
||||||
container->end();
|
container->end();
|
||||||
|
container.reset(); // Release ownership of the container's shared_ptr
|
||||||
|
|
||||||
bool valid = json::parse_json(response, [this](JsonObject root) -> bool {
|
valid = json::parse_json(response, [this_update](JsonObject root) -> bool {
|
||||||
if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
|
if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
|
||||||
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this->update_info_.title = root["name"].as<std::string>();
|
|
||||||
this->update_info_.latest_version = root["version"].as<std::string>();
|
|
||||||
|
|
||||||
for (auto build : root["builds"].as<JsonArray>()) {
|
|
||||||
if (!build.containsKey("chipFamily")) {
|
|
||||||
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (build["chipFamily"] == ESPHOME_VARIANT) {
|
this_update->update_info_.title = root["name"].as<std::string>();
|
||||||
if (!build.containsKey("ota")) {
|
this_update->update_info_.latest_version = root["version"].as<std::string>();
|
||||||
|
|
||||||
|
for (auto build : root["builds"].as<JsonArray>()) {
|
||||||
|
if (!build.containsKey("chipFamily")) {
|
||||||
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto ota = build["ota"];
|
if (build["chipFamily"] == ESPHOME_VARIANT) {
|
||||||
if (!ota.containsKey("path") || !ota.containsKey("md5")) {
|
if (!build.containsKey("ota")) {
|
||||||
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
auto ota = build["ota"];
|
||||||
|
if (!ota.containsKey("path") || !ota.containsKey("md5")) {
|
||||||
|
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this_update->update_info_.firmware_url = ota["path"].as<std::string>();
|
||||||
|
this_update->update_info_.md5 = ota["md5"].as<std::string>();
|
||||||
|
|
||||||
|
if (ota.containsKey("summary"))
|
||||||
|
this_update->update_info_.summary = ota["summary"].as<std::string>();
|
||||||
|
if (ota.containsKey("release_url"))
|
||||||
|
this_update->update_info_.release_url = ota["release_url"].as<std::string>();
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
this->update_info_.firmware_url = ota["path"].as<std::string>();
|
|
||||||
this->update_info_.md5 = ota["md5"].as<std::string>();
|
|
||||||
|
|
||||||
if (ota.containsKey("summary"))
|
|
||||||
this->update_info_.summary = ota["summary"].as<std::string>();
|
|
||||||
if (ota.containsKey("release_url"))
|
|
||||||
this->update_info_.release_url = ota["release_url"].as<std::string>();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
return false;
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str());
|
std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str());
|
||||||
this->status_set_error(msg.c_str());
|
this_update->status_set_error(msg.c_str());
|
||||||
return;
|
UPDATE_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge source_url_ and this->update_info_.firmware_url
|
// Merge source_url_ and this_update->update_info_.firmware_url
|
||||||
if (this->update_info_.firmware_url.find("http") == std::string::npos) {
|
if (this_update->update_info_.firmware_url.find("http") == std::string::npos) {
|
||||||
std::string path = this->update_info_.firmware_url;
|
std::string path = this_update->update_info_.firmware_url;
|
||||||
if (path[0] == '/') {
|
if (path[0] == '/') {
|
||||||
std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8));
|
std::string domain = this_update->source_url_.substr(0, this_update->source_url_.find('/', 8));
|
||||||
this->update_info_.firmware_url = domain + path;
|
this_update->update_info_.firmware_url = domain + path;
|
||||||
} else {
|
} else {
|
||||||
std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1);
|
std::string domain = this_update->source_url_.substr(0, this_update->source_url_.rfind('/') + 1);
|
||||||
this->update_info_.firmware_url = domain + path;
|
this_update->update_info_.firmware_url = domain + path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string current_version;
|
{ // Ensures the current version string falls out of scope and deallocates before the task ends
|
||||||
|
std::string current_version;
|
||||||
#ifdef ESPHOME_PROJECT_VERSION
|
#ifdef ESPHOME_PROJECT_VERSION
|
||||||
current_version = ESPHOME_PROJECT_VERSION;
|
current_version = ESPHOME_PROJECT_VERSION;
|
||||||
#else
|
#else
|
||||||
current_version = ESPHOME_VERSION;
|
current_version = ESPHOME_VERSION;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
this->update_info_.current_version = current_version;
|
this_update->update_info_.current_version = current_version;
|
||||||
|
|
||||||
if (this->update_info_.latest_version.empty() || this->update_info_.latest_version == update_info_.current_version) {
|
|
||||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
|
||||||
} else {
|
|
||||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->update_info_.has_progress = false;
|
if (this_update->update_info_.latest_version.empty() ||
|
||||||
this->update_info_.progress = 0.0f;
|
this_update->update_info_.latest_version == this_update->update_info_.current_version) {
|
||||||
|
this_update->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||||
|
} else {
|
||||||
|
this_update->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
this->status_clear_error();
|
this_update->update_info_.has_progress = false;
|
||||||
this->publish_state();
|
this_update->update_info_.progress = 0.0f;
|
||||||
|
|
||||||
|
this_update->status_clear_error();
|
||||||
|
this_update->publish_state();
|
||||||
|
|
||||||
|
UPDATE_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequestUpdate::perform(bool force) {
|
void HttpRequestUpdate::perform(bool force) {
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
#include "esphome/components/http_request/ota/ota_http_request.h"
|
#include "esphome/components/http_request/ota/ota_http_request.h"
|
||||||
#include "esphome/components/update/update_entity.h"
|
#include "esphome/components/update/update_entity.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace http_request {
|
namespace http_request {
|
||||||
|
|
||||||
@ -29,6 +33,11 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
|
|||||||
HttpRequestComponent *request_parent_;
|
HttpRequestComponent *request_parent_;
|
||||||
OtaHttpRequestComponent *ota_parent_;
|
OtaHttpRequestComponent *ota_parent_;
|
||||||
std::string source_url_;
|
std::string source_url_;
|
||||||
|
|
||||||
|
static void update_task(void *params);
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
TaskHandle_t update_task_handle_{nullptr};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace http_request
|
} // namespace http_request
|
||||||
|
Loading…
x
Reference in New Issue
Block a user