ESPHome  2024.4.0
nextion_upload_idf.cpp
Go to the documentation of this file.
1 #include "nextion.h"
2 
3 #ifdef USE_ESP_IDF
4 #ifdef USE_NEXTION_TFT_UPLOAD
5 
7 #include "esphome/core/defines.h"
8 #include "esphome/core/util.h"
9 #include "esphome/core/log.h"
11 
12 #include <esp_heap_caps.h>
13 #include <esp_http_client.h>
14 #include <cinttypes>
15 
16 namespace esphome {
17 namespace nextion {
18 static const char *const TAG = "nextion.upload.idf";
19 
20 // Followed guide
21 // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
22 
23 int Nextion::upload_range(const std::string &url, int range_start) {
24  ESP_LOGVV(TAG, "url: %s", url.c_str());
25  uint range_size = this->tft_size_ - range_start;
26  ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_);
27  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
28  int range_end = (range_start == 0) ? std::min(this->tft_size_, 16383) : this->tft_size_;
29  if (range_size <= 0 or range_end <= range_start) {
30  ESP_LOGE(TAG, "Invalid range");
31  ESP_LOGD(TAG, "Range start: %i", range_start);
32  ESP_LOGD(TAG, "Range end: %i", range_end);
33  ESP_LOGD(TAG, "Range size: %i", range_size);
34  return -1;
35  }
36 
37  esp_http_client_config_t config = {
38  .url = url.c_str(),
39  .cert_pem = nullptr,
40  .disable_auto_redirect = false,
41  .max_redirection_count = 10,
42  };
43  esp_http_client_handle_t client = esp_http_client_init(&config);
44 
45  char range_header[64];
46  sprintf(range_header, "bytes=%d-%d", range_start, range_end);
47  ESP_LOGV(TAG, "Requesting range: %s", range_header);
48  esp_http_client_set_header(client, "Range", range_header);
49  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
50 
51  ESP_LOGV(TAG, "Opening http connetion");
52  esp_err_t err;
53  if ((err = esp_http_client_open(client, 0)) != ESP_OK) {
54  ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
55  esp_http_client_cleanup(client);
56  return -1;
57  }
58 
59  ESP_LOGV(TAG, "Fetch content length");
60  int content_length = esp_http_client_fetch_headers(client);
61  ESP_LOGV(TAG, "content_length = %d", content_length);
62  if (content_length <= 0) {
63  ESP_LOGE(TAG, "Failed to get content length: %d", content_length);
64  esp_http_client_cleanup(client);
65  return -1;
66  }
67 
68  int total_read_len = 0, read_len;
69 
70  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
71  ESP_LOGV(TAG, "Allocate buffer");
72  uint8_t *buffer = new uint8_t[4096];
73  std::string recv_string;
74  if (buffer == nullptr) {
75  ESP_LOGE(TAG, "Failed to allocate memory for buffer");
76  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
77  } else {
78  ESP_LOGV(TAG, "Memory for buffer allocated successfully");
79 
80  while (true) {
81  App.feed_wdt();
82  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
83  int read_len = esp_http_client_read(client, reinterpret_cast<char *>(buffer), 4096);
84  ESP_LOGVV(TAG, "Read %d bytes from HTTP client, writing to UART", read_len);
85  if (read_len > 0) {
86  this->write_array(buffer, read_len);
87  ESP_LOGVV(TAG, "Write to UART successful");
88  this->recv_ret_string_(recv_string, 5000, true);
89  this->content_length_ -= read_len;
90  ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes, heap is %" PRIu32 " bytes",
91  100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_,
92  esp_get_free_heap_size());
93 
94  if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
95  ESP_LOGD(
96  TAG, "recv_string [%s]",
97  format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
98  uint32_t result = 0;
99  for (int j = 0; j < 4; ++j) {
100  result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
101  }
102  if (result > 0) {
103  ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result);
104  this->content_length_ = this->tft_size_ - result;
105  // Deallocate the buffer when done
106  ESP_LOGV(TAG, "Deallocate buffer");
107  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
108  delete[] buffer;
109  ESP_LOGVV(TAG, "Memory for buffer deallocated");
110  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
111  ESP_LOGV(TAG, "Close http client");
112  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
113  esp_http_client_close(client);
114  esp_http_client_cleanup(client);
115  ESP_LOGVV(TAG, "Client closed");
116  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
117  return result;
118  }
119  } else if (recv_string[0] != 0x05) { // 0x05 == "ok"
120  ESP_LOGE(
121  TAG, "Invalid response from Nextion: [%s]",
122  format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
123  ESP_LOGV(TAG, "Deallocate buffer");
124  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
125  delete[] buffer;
126  ESP_LOGVV(TAG, "Memory for buffer deallocated");
127  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
128  ESP_LOGV(TAG, "Close http client");
129  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
130  esp_http_client_close(client);
131  esp_http_client_cleanup(client);
132  ESP_LOGVV(TAG, "Client closed");
133  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
134  return -1;
135  }
136 
137  recv_string.clear();
138  } else if (read_len == 0) {
139  ESP_LOGV(TAG, "End of HTTP response reached");
140  break; // Exit the loop if there is no more data to read
141  } else {
142  ESP_LOGE(TAG, "Failed to read from HTTP client, error code: %d", read_len);
143  break; // Exit the loop on error
144  }
145  }
146 
147  // Deallocate the buffer when done
148  ESP_LOGV(TAG, "Deallocate buffer");
149  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
150  delete[] buffer;
151  ESP_LOGVV(TAG, "Memory for buffer deallocated");
152  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
153  }
154  ESP_LOGV(TAG, "Close http client");
155  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
156  esp_http_client_close(client);
157  esp_http_client_cleanup(client);
158  ESP_LOGVV(TAG, "Client closed");
159  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
160  return range_end + 1;
161 }
162 
163 bool Nextion::upload_tft() {
164  ESP_LOGD(TAG, "Nextion TFT upload requested");
165  ESP_LOGD(TAG, "url: %s", this->tft_url_.c_str());
166 
167  if (this->is_updating_) {
168  ESP_LOGW(TAG, "Currently updating");
169  return false;
170  }
171 
172  if (!network::is_connected()) {
173  ESP_LOGE(TAG, "Network is not connected");
174  return false;
175  }
176 
177  this->is_updating_ = true;
178 
179  // Define the configuration for the HTTP client
180  ESP_LOGV(TAG, "Establishing connection to HTTP server");
181  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
182  esp_http_client_config_t config = {
183  .url = this->tft_url_.c_str(),
184  .cert_pem = nullptr,
185  .method = HTTP_METHOD_HEAD,
186  .timeout_ms = 15000,
187  .disable_auto_redirect = false,
188  .max_redirection_count = 10,
189  };
190 
191  // Initialize the HTTP client with the configuration
192  ESP_LOGV(TAG, "Initializing HTTP client");
193  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
194  esp_http_client_handle_t http = esp_http_client_init(&config);
195  if (!http) {
196  ESP_LOGE(TAG, "Failed to initialize HTTP client.");
197  return this->upload_end(false);
198  }
199 
200  // Perform the HTTP request
201  ESP_LOGV(TAG, "Check if the client could connect");
202  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
203  esp_err_t err = esp_http_client_perform(http);
204  if (err != ESP_OK) {
205  ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
206  esp_http_client_cleanup(http);
207  return this->upload_end(false);
208  }
209 
210  // Check the HTTP Status Code
211  ESP_LOGV(TAG, "Check the HTTP Status Code");
212  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
213  int status_code = esp_http_client_get_status_code(http);
214  ESP_LOGV(TAG, "HTTP Status Code: %d", status_code);
215  size_t tft_file_size = esp_http_client_get_content_length(http);
216  ESP_LOGD(TAG, "TFT file size: %zu", tft_file_size);
217 
218  ESP_LOGD(TAG, "Close HTTP connection");
219  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
220  esp_http_client_close(http);
221  esp_http_client_cleanup(http);
222  ESP_LOGVV(TAG, "Connection closed");
223  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
224 
225  if (tft_file_size < 4096) {
226  ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size);
227  return this->upload_end(false);
228  } else {
229  ESP_LOGV(TAG, "File size check passed. Proceeding...");
230  }
231  this->content_length_ = tft_file_size;
232  this->tft_size_ = tft_file_size;
233 
234  ESP_LOGD(TAG, "Updating Nextion");
235 
236  // The Nextion will ignore the update command if it is sleeping
237  ESP_LOGV(TAG, "Wake-up Nextion");
238  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
239  this->send_command_("sleep=0");
240  this->set_backlight_brightness(1.0);
241  vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT
242 
243  App.feed_wdt();
244  char command[128];
245  // Tells the Nextion the content length of the tft file and baud rate it will be sent at
246  // Once the Nextion accepts the command it will wait until the file is successfully uploaded
247  // If it fails for any reason a power cycle of the display will be needed
248  sprintf(command, "whmi-wris %d,%" PRIu32 ",1", this->content_length_, this->parent_->get_baud_rate());
249 
250  // Clear serial receive buffer
251  ESP_LOGV(TAG, "Clear serial receive buffer");
252  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
253  uint8_t d;
254  while (this->available()) {
255  this->read_byte(&d);
256  };
257 
258  ESP_LOGV(TAG, "Send update instruction: %s", command);
259  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
260  this->send_command_(command);
261 
262  std::string response;
263  ESP_LOGV(TAG, "Waiting for upgrade response");
264  this->recv_ret_string_(response, 5000, true); // This can take some time to return
265 
266  // The Nextion display will, if it's ready to accept data, send a 0x05 byte.
267  ESP_LOGD(TAG, "Upgrade response is [%s] - %zu bytes",
268  format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
269  response.length());
270  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
271 
272  if (response.find(0x05) != std::string::npos) {
273  ESP_LOGV(TAG, "Preparation for tft update done");
274  } else {
275  ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str());
276  return this->upload_end(false);
277  }
278 
279  ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d, Heap Size %" PRIu32, this->tft_url_.c_str(),
280  content_length_, esp_get_free_heap_size());
281 
282  ESP_LOGV(TAG, "Starting transfer by chunks loop");
283  ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
284  int result = 0;
285  while (content_length_ > 0) {
286  result = upload_range(this->tft_url_.c_str(), result);
287  if (result < 0) {
288  ESP_LOGE(TAG, "Error updating Nextion!");
289  return this->upload_end(false);
290  }
291  App.feed_wdt();
292  ESP_LOGV(TAG, "Heap Size %" PRIu32 ", Bytes left %d", esp_get_free_heap_size(), content_length_);
293  }
294 
295  ESP_LOGD(TAG, "Successfully updated Nextion!");
296 
297  return upload_end(true);
298 }
299 
300 bool Nextion::upload_end(bool successful) {
301  this->is_updating_ = false;
302  ESP_LOGD(TAG, "Restarting Nextion");
303  this->soft_reset();
304  vTaskDelay(pdMS_TO_TICKS(1500)); // NOLINT
305  if (successful) {
306  ESP_LOGD(TAG, "Restarting ESPHome");
307  esp_restart(); // NOLINT(readability-static-accessed-through-instance)
308  }
309  return successful;
310 }
311 
312 } // namespace nextion
313 } // namespace esphome
314 
315 #endif // USE_NEXTION_TFT_UPLOAD
316 #endif // USE_ESP_IDF
int upload_range(const std::string &url, int range_start)
will request 4096 bytes chunks from the web server and send each to Nextion
uint32_t get_baud_rate() const
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
Definition: helpers.cpp:361
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
bool send_command_(const std::string &command)
Manually send a raw command to the display and don&#39;t wait for an acknowledgement packet.
Definition: nextion.cpp:29
UARTComponent * parent_
Definition: uart.h:68
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition: util.cpp:15
bool upload_end(bool successful)
Ends the upload process, restart Nextion and, if successful, restarts ESP.
bool read_byte(uint8_t *data)
Definition: uart.h:29
Application App
Global storage of Application pointer - only one Application can exist.
void set_backlight_brightness(float brightness)
Set the brightness of the backlight.
void soft_reset()
Softreset the Nextion.
bool upload_tft()
Upload the tft file and soft reset Nextion.
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag)
Definition: nextion.cpp:890