ESPHome  2024.12.4
http_request_update.cpp
Go to the documentation of this file.
1 #include "http_request_update.h"
2 
4 #include "esphome/core/version.h"
5 
8 
9 namespace esphome {
10 namespace http_request {
11 
12 // The update function runs in a task only on ESP32s.
13 #ifdef USE_ESP32
14 #define UPDATE_RETURN vTaskDelete(nullptr) // Delete the current update task
15 #else
16 #define UPDATE_RETURN return
17 #endif
18 
19 static const char *const TAG = "http_request.update";
20 
21 static const size_t MAX_READ_SIZE = 256;
22 
24  this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) {
25  if (state == ota::OTAState::OTA_IN_PROGRESS) {
27  this->update_info_.has_progress = true;
28  this->update_info_.progress = progress;
29  this->publish_state();
30  } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
32  this->status_set_error("Failed to install firmware");
33  this->publish_state();
34  }
35  });
36 }
37 
39 #ifdef USE_ESP32
40  xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_);
41 #else
42  this->update_task(this);
43 #endif
44 }
45 
46 void HttpRequestUpdate::update_task(void *params) {
47  HttpRequestUpdate *this_update = (HttpRequestUpdate *) params;
48 
49  auto container = this_update->request_parent_->get(this_update->source_url_);
50 
51  if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
52  std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str());
53  this_update->status_set_error(msg.c_str());
54  UPDATE_RETURN;
55  }
56 
58  uint8_t *data = allocator.allocate(container->content_length);
59  if (data == nullptr) {
60  std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
61  this_update->status_set_error(msg.c_str());
62  container->end();
63  UPDATE_RETURN;
64  }
65 
66  size_t read_index = 0;
67  while (container->get_bytes_read() < container->content_length) {
68  int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
69 
70  yield();
71 
72  read_index += read_bytes;
73  }
74 
75  bool valid = false;
76  { // Ensures the response string falls out of scope and deallocates before the task ends
77  std::string response((char *) data, read_index);
78  allocator.deallocate(data, container->content_length);
79 
80  container->end();
81  container.reset(); // Release ownership of the container's shared_ptr
82 
83  valid = json::parse_json(response, [this_update](JsonObject root) -> bool {
84  if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
85  ESP_LOGE(TAG, "Manifest does not contain required fields");
86  return false;
87  }
88  this_update->update_info_.title = root["name"].as<std::string>();
89  this_update->update_info_.latest_version = root["version"].as<std::string>();
90 
91  for (auto build : root["builds"].as<JsonArray>()) {
92  if (!build.containsKey("chipFamily")) {
93  ESP_LOGE(TAG, "Manifest does not contain required fields");
94  return false;
95  }
96  if (build["chipFamily"] == ESPHOME_VARIANT) {
97  if (!build.containsKey("ota")) {
98  ESP_LOGE(TAG, "Manifest does not contain required fields");
99  return false;
100  }
101  auto ota = build["ota"];
102  if (!ota.containsKey("path") || !ota.containsKey("md5")) {
103  ESP_LOGE(TAG, "Manifest does not contain required fields");
104  return false;
105  }
106  this_update->update_info_.firmware_url = ota["path"].as<std::string>();
107  this_update->update_info_.md5 = ota["md5"].as<std::string>();
108 
109  if (ota.containsKey("summary"))
110  this_update->update_info_.summary = ota["summary"].as<std::string>();
111  if (ota.containsKey("release_url"))
112  this_update->update_info_.release_url = ota["release_url"].as<std::string>();
113 
114  return true;
115  }
116  }
117  return false;
118  });
119  }
120 
121  if (!valid) {
122  std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str());
123  this_update->status_set_error(msg.c_str());
124  UPDATE_RETURN;
125  }
126 
127  // Merge source_url_ and this_update->update_info_.firmware_url
128  if (this_update->update_info_.firmware_url.find("http") == std::string::npos) {
129  std::string path = this_update->update_info_.firmware_url;
130  if (path[0] == '/') {
131  std::string domain = this_update->source_url_.substr(0, this_update->source_url_.find('/', 8));
132  this_update->update_info_.firmware_url = domain + path;
133  } else {
134  std::string domain = this_update->source_url_.substr(0, this_update->source_url_.rfind('/') + 1);
135  this_update->update_info_.firmware_url = domain + path;
136  }
137  }
138 
139  { // Ensures the current version string falls out of scope and deallocates before the task ends
140  std::string current_version;
141 #ifdef ESPHOME_PROJECT_VERSION
142  current_version = ESPHOME_PROJECT_VERSION;
143 #else
144  current_version = ESPHOME_VERSION;
145 #endif
146 
147  this_update->update_info_.current_version = current_version;
148  }
149 
150  if (this_update->update_info_.latest_version.empty() ||
151  this_update->update_info_.latest_version == this_update->update_info_.current_version) {
152  this_update->state_ = update::UPDATE_STATE_NO_UPDATE;
153  } else {
154  this_update->state_ = update::UPDATE_STATE_AVAILABLE;
155  }
156 
157  this_update->update_info_.has_progress = false;
158  this_update->update_info_.progress = 0.0f;
159 
160  this_update->status_clear_error();
161  this_update->publish_state();
162 
163  UPDATE_RETURN;
164 }
165 
166 void HttpRequestUpdate::perform(bool force) {
167  if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
168  return;
169  }
170 
172  this->publish_state();
173 
174  this->ota_parent_->set_md5(this->update_info.md5);
176  // Flash in the next loop
177  this->defer([this]() { this->ota_parent_->flash(); });
178 }
179 
180 } // namespace http_request
181 } // namespace esphome
std::shared_ptr< HttpContainer > get(std::string url)
Definition: http_request.h:122
bool parse_json(const std::string &data, const json_parse_t &f)
Parse a JSON string and run the provided json parse function if it&#39;s valid.
Definition: json_util.cpp:65
const UpdateState & state
Definition: update_entity.h:40
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition: component.cpp:130
T * allocate(size_t n)
Definition: helpers.h:690
void add_on_state_callback(std::function< void(ota::OTAState, float, uint8_t)> &&callback)
Definition: ota_backend.h:65
void status_set_error(const char *message="unspecified")
Definition: component.cpp:159
const UpdateInfo & update_info
Definition: update_entity.h:39
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:320
void deallocate(T *p, size_t n)
Definition: helpers.h:709
void status_clear_error()
Definition: component.cpp:172
void IRAM_ATTR HOT yield()
Definition: core.cpp:24
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
An STL allocator that uses SPI or internal RAM.
Definition: helpers.h:675