ESPHome  2024.11.0
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 static const char *const TAG = "http_request.update";
13 
14 static const size_t MAX_READ_SIZE = 256;
15 
17  this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) {
18  if (state == ota::OTAState::OTA_IN_PROGRESS) {
20  this->update_info_.has_progress = true;
21  this->update_info_.progress = progress;
22  this->publish_state();
23  } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
25  this->status_set_error("Failed to install firmware");
26  this->publish_state();
27  }
28  });
29 }
30 
32  auto container = this->request_parent_->get(this->source_url_);
33 
34  if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
35  std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str());
36  this->status_set_error(msg.c_str());
37  return;
38  }
39 
41  uint8_t *data = allocator.allocate(container->content_length);
42  if (data == nullptr) {
43  std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
44  this->status_set_error(msg.c_str());
45  container->end();
46  return;
47  }
48 
49  size_t read_index = 0;
50  while (container->get_bytes_read() < container->content_length) {
51  int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
52 
53  App.feed_wdt();
54  yield();
55 
56  read_index += read_bytes;
57  }
58 
59  std::string response((char *) data, read_index);
60  allocator.deallocate(data, container->content_length);
61 
62  container->end();
63 
64  bool valid = json::parse_json(response, [this](JsonObject root) -> bool {
65  if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
66  ESP_LOGE(TAG, "Manifest does not contain required fields");
67  return false;
68  }
69  this->update_info_.title = root["name"].as<std::string>();
70  this->update_info_.latest_version = root["version"].as<std::string>();
71 
72  for (auto build : root["builds"].as<JsonArray>()) {
73  if (!build.containsKey("chipFamily")) {
74  ESP_LOGE(TAG, "Manifest does not contain required fields");
75  return false;
76  }
77  if (build["chipFamily"] == ESPHOME_VARIANT) {
78  if (!build.containsKey("ota")) {
79  ESP_LOGE(TAG, "Manifest does not contain required fields");
80  return false;
81  }
82  auto ota = build["ota"];
83  if (!ota.containsKey("path") || !ota.containsKey("md5")) {
84  ESP_LOGE(TAG, "Manifest does not contain required fields");
85  return false;
86  }
87  this->update_info_.firmware_url = ota["path"].as<std::string>();
88  this->update_info_.md5 = ota["md5"].as<std::string>();
89 
90  if (ota.containsKey("summary"))
91  this->update_info_.summary = ota["summary"].as<std::string>();
92  if (ota.containsKey("release_url"))
93  this->update_info_.release_url = ota["release_url"].as<std::string>();
94 
95  return true;
96  }
97  }
98  return false;
99  });
100 
101  if (!valid) {
102  std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str());
103  this->status_set_error(msg.c_str());
104  return;
105  }
106 
107  // Merge source_url_ and this->update_info_.firmware_url
108  if (this->update_info_.firmware_url.find("http") == std::string::npos) {
109  std::string path = this->update_info_.firmware_url;
110  if (path[0] == '/') {
111  std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8));
112  this->update_info_.firmware_url = domain + path;
113  } else {
114  std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1);
115  this->update_info_.firmware_url = domain + path;
116  }
117  }
118 
119  std::string current_version;
120 #ifdef ESPHOME_PROJECT_VERSION
121  current_version = ESPHOME_PROJECT_VERSION;
122 #else
123  current_version = ESPHOME_VERSION;
124 #endif
125 
126  this->update_info_.current_version = current_version;
127 
130  } else {
132  }
133 
134  this->update_info_.has_progress = false;
135  this->update_info_.progress = 0.0f;
136 
137  this->status_clear_error();
138  this->publish_state();
139 }
140 
141 void HttpRequestUpdate::perform(bool force) {
142  if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
143  return;
144  }
145 
147  this->publish_state();
148 
149  this->ota_parent_->set_md5(this->update_info.md5);
151  // Flash in the next loop
152  this->defer([this]() { this->ota_parent_->flash(); });
153 }
154 
155 } // namespace http_request
156 } // 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:681
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:315
Application App
Global storage of Application pointer - only one Application can exist.
void deallocate(T *p, size_t n)
Definition: helpers.h:700
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:666