ESPHome 2025.6.3
Loading...
Searching...
No Matches
nextion_upload_idf.cpp
Go to the documentation of this file.
1#include "nextion.h"
2
3#ifdef USE_NEXTION_TFT_UPLOAD
4#ifdef USE_ESP_IDF
5
8#include "esphome/core/util.h"
9#include "esphome/core/log.h"
11#include <cinttypes>
12#include <esp_heap_caps.h>
13#include <esp_http_client.h>
14
15namespace esphome {
16namespace nextion {
17static const char *const TAG = "nextion.upload.idf";
18
19// Followed guide
20// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
21
22int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start) {
23 uint32_t range_size = this->tft_size_ - range_start;
24 ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
25 uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1;
26 ESP_LOGD(TAG, "Range start: %" PRIu32, range_start);
27 if (range_size <= 0 or range_end <= range_start) {
28 ESP_LOGD(TAG, "Range end: %" PRIu32, range_end);
29 ESP_LOGD(TAG, "Range size: %" PRIu32, range_size);
30 ESP_LOGE(TAG, "Invalid range");
31 return -1;
32 }
33
34 char range_header[32];
35 sprintf(range_header, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end);
36 ESP_LOGV(TAG, "Range: %s", range_header);
37 esp_http_client_set_header(http_client, "Range", range_header);
38 ESP_LOGV(TAG, "Open HTTP");
39 esp_err_t err = esp_http_client_open(http_client, 0);
40 if (err != ESP_OK) {
41 ESP_LOGE(TAG, "HTTP open failed: %s", esp_err_to_name(err));
42 return -1;
43 }
44
45 ESP_LOGV(TAG, "Fetch length");
46 const int chunk_size = esp_http_client_fetch_headers(http_client);
47 ESP_LOGV(TAG, "Length: %d", chunk_size);
48 if (chunk_size <= 0) {
49 ESP_LOGE(TAG, "Get length failed: %d", chunk_size);
50 return -1;
51 }
52
53 // Allocate the buffer dynamically
55 uint8_t *buffer = allocator.allocate(4096);
56 if (!buffer) {
57 ESP_LOGE(TAG, "Buffer alloc failed");
58 return -1;
59 }
60
61 std::string recv_string;
62 while (true) {
63 App.feed_wdt();
64 const uint16_t buffer_size =
65 this->content_length_ < 4096 ? this->content_length_ : 4096; // Limits buffer to the remaining data
66 ESP_LOGV(TAG, "Fetch %" PRIu16 " bytes", buffer_size);
67 uint16_t read_len = 0;
68 int partial_read_len = 0;
69 uint8_t retries = 0;
70 // Attempt to read the chunk with retries.
71 while (retries < 5 && read_len < buffer_size) {
72 partial_read_len =
73 esp_http_client_read(http_client, reinterpret_cast<char *>(buffer) + read_len, buffer_size - read_len);
74 if (partial_read_len > 0) {
75 read_len += partial_read_len; // Accumulate the total read length.
76 // Reset retries on successful read.
77 retries = 0;
78 } else {
79 // If no data was read, increment retries.
80 retries++;
81 vTaskDelay(pdMS_TO_TICKS(2)); // NOLINT
82 }
83 App.feed_wdt(); // Feed the watchdog timer.
84 }
85 if (read_len != buffer_size) {
86 // Did not receive the full package within the timeout period
87 ESP_LOGE(TAG, "Read failed: %" PRIu16 "/%" PRIu16 " bytes", read_len, buffer_size);
88 // Deallocate buffer
89 allocator.deallocate(buffer, 4096);
90 buffer = nullptr;
91 return -1;
92 }
93 ESP_LOGV(TAG, "Fetched %d bytes", read_len);
94 if (read_len > 0) {
95 recv_string.clear();
96 this->write_array(buffer, buffer_size);
97 App.feed_wdt();
98 this->recv_ret_string_(recv_string, upload_first_chunk_sent_ ? 500 : 5000, true);
99 this->content_length_ -= read_len;
100 const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_;
101#ifdef USE_PSRAM
102 ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 "+%" PRIu32 ")", upload_percentage,
103 this->content_length_, static_cast<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)),
104 static_cast<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)));
105#else
106 ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 ")", upload_percentage, this->content_length_,
107 static_cast<uint32_t>(esp_get_free_heap_size()));
108#endif
110 if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
111 ESP_LOGD(TAG, "Recv: [%s]",
112 format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
113 uint32_t result = 0;
114 for (int j = 0; j < 4; ++j) {
115 result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
116 }
117 if (result > 0) {
118 ESP_LOGI(TAG, "New range: %" PRIu32, result);
119 this->content_length_ = this->tft_size_ - result;
120 range_start = result;
121 } else {
122 range_start = range_end + 1;
123 }
124 // Deallocate buffer
125 allocator.deallocate(buffer, 4096);
126 buffer = nullptr;
127 return range_end + 1;
128 } else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok"
129 ESP_LOGE(TAG, "Invalid response: [%s]",
130 format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
131 // Deallocate buffer
132 allocator.deallocate(buffer, 4096);
133 buffer = nullptr;
134 return -1;
135 }
136
137 recv_string.clear();
138 } else if (read_len == 0) {
139 ESP_LOGV(TAG, "HTTP end");
140 break; // Exit the loop if there is no more data to read
141 } else {
142 ESP_LOGE(TAG, "HTTP read failed: %" PRIu16, read_len);
143 break; // Exit the loop on error
144 }
145 }
146 range_start = range_end + 1;
147 // Deallocate buffer
148 allocator.deallocate(buffer, 4096);
149 buffer = nullptr;
150 return range_end + 1;
151}
152
153bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
154 ESP_LOGD(TAG, "TFT upload requested");
155 ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse));
156 ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str());
157
158 if (this->is_updating_) {
159 ESP_LOGW(TAG, "Upload in progress");
160 return false;
161 }
162
163 if (!network::is_connected()) {
164 ESP_LOGE(TAG, "No network");
165 return false;
166 }
167
168 this->is_updating_ = true;
169
170 if (exit_reparse) {
171 ESP_LOGD(TAG, "Exit reparse mode");
172 if (!this->set_protocol_reparse_mode(false)) {
173 ESP_LOGW(TAG, "Exit reparse failed");
174 return false;
175 }
176 }
177
178 // Check if baud rate is supported
180 static const std::vector<uint32_t> SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600,
181 115200, 230400, 250000, 256000, 512000, 921600};
182 if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) {
183 baud_rate = this->original_baud_rate_;
184 }
185 ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
186
187 // Define the configuration for the HTTP client
188 ESP_LOGV(TAG, "Init HTTP client");
189 ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
190 esp_http_client_config_t config = {
191 .url = this->tft_url_.c_str(),
192 .cert_pem = nullptr,
193 .method = HTTP_METHOD_HEAD,
194 .timeout_ms = 15000,
195 .disable_auto_redirect = false,
196 .max_redirection_count = 10,
197 };
198 // Initialize the HTTP client with the configuration
199 esp_http_client_handle_t http_client = esp_http_client_init(&config);
200 if (!http_client) {
201 ESP_LOGE(TAG, "HTTP init failed");
202 return this->upload_end_(false);
203 }
204
205 esp_err_t err = esp_http_client_set_header(http_client, "Connection", "keep-alive");
206 if (err != ESP_OK) {
207 ESP_LOGE(TAG, "Set header failed: %s", esp_err_to_name(err));
208 esp_http_client_cleanup(http_client);
209 return this->upload_end_(false);
210 }
211
212 // Perform the HTTP request
213 ESP_LOGV(TAG, "Check connection");
214 ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
215 err = esp_http_client_perform(http_client);
216 if (err != ESP_OK) {
217 ESP_LOGE(TAG, "HTTP failed: %s", esp_err_to_name(err));
218 esp_http_client_cleanup(http_client);
219 return this->upload_end_(false);
220 }
221
222 // Check the HTTP Status Code
223 ESP_LOGV(TAG, "Check status");
224 ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
225 int status_code = esp_http_client_get_status_code(http_client);
226 if (status_code != 200 && status_code != 206) {
227 return this->upload_end_(false);
228 }
229
230 this->tft_size_ = esp_http_client_get_content_length(http_client);
231
232 ESP_LOGD(TAG, "TFT size: %zu bytes", this->tft_size_);
233 if (this->tft_size_ < 4096 || this->tft_size_ > 134217728) {
234 ESP_LOGE(TAG, "Size check failed");
235 ESP_LOGD(TAG, "Close HTTP");
236 esp_http_client_close(http_client);
237 esp_http_client_cleanup(http_client);
238 ESP_LOGV(TAG, "Connection closed");
239 return this->upload_end_(false);
240 } else {
241 ESP_LOGV(TAG, "Size check OK");
242 }
243 this->content_length_ = this->tft_size_;
244
245 ESP_LOGD(TAG, "Uploading");
246
247 // The Nextion will ignore the upload command if it is sleeping
248 ESP_LOGV(TAG, "Wake-up");
249 this->ignore_is_setup_ = true;
250 this->send_command_("sleep=0");
251 this->send_command_("dim=100");
252 vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT
253 ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
254
255 App.feed_wdt();
256 char command[128];
257 // Tells the Nextion the content length of the tft file and baud rate it will be sent at
258 // Once the Nextion accepts the command it will wait until the file is successfully uploaded
259 // If it fails for any reason a power cycle of the display will be needed
260 sprintf(command, "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate);
261
262 // Clear serial receive buffer
263 ESP_LOGV(TAG, "Clear RX buffer");
264 this->reset_(false);
265 vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT
266 ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
267
268 ESP_LOGV(TAG, "Upload cmd: %s", command);
269 this->send_command_(command);
270
271 if (baud_rate != this->original_baud_rate_) {
272 ESP_LOGD(TAG, "Baud: %" PRIu32 "->%" PRIu32, this->original_baud_rate_, baud_rate);
273 this->parent_->set_baud_rate(baud_rate);
274 this->parent_->load_settings();
275 }
276
277 std::string response;
278 ESP_LOGV(TAG, "Wait upload resp");
279 this->recv_ret_string_(response, 5000, true); // This can take some time to return
280
281 // The Nextion display will, if it's ready to accept data, send a 0x05 byte.
282 ESP_LOGD(TAG, "Upload resp: [%s] %zu B",
283 format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
284 response.length());
285 ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
286
287 if (response.find(0x05) != std::string::npos) {
288 ESP_LOGV(TAG, "Upload prep done");
289 } else {
290 ESP_LOGE(TAG, "Upload prep failed %d '%s'", response[0], response.c_str());
291 ESP_LOGD(TAG, "Close HTTP");
292 esp_http_client_close(http_client);
293 esp_http_client_cleanup(http_client);
294 ESP_LOGV(TAG, "Connection closed");
295 return this->upload_end_(false);
296 }
297
298 ESP_LOGV(TAG, "Set method to GET");
299 esp_err_t set_method_result = esp_http_client_set_method(http_client, HTTP_METHOD_GET);
300 if (set_method_result != ESP_OK) {
301 ESP_LOGE(TAG, "Set GET failed: %s", esp_err_to_name(set_method_result));
302 return this->upload_end_(false);
303 }
304
305 ESP_LOGD(TAG, "Uploading TFT:");
306 ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str());
307 ESP_LOGD(TAG, " Size: %" PRIu32 " bytes", this->content_length_);
308 ESP_LOGD(TAG, " Heap: %" PRIu32, esp_get_free_heap_size());
309
310 // Proceed with the content download as before
311
312 ESP_LOGV(TAG, "Start chunk transfer");
313
314 uint32_t position = 0;
315 while (this->content_length_ > 0) {
316 int upload_result = upload_by_chunks_(http_client, position);
317 if (upload_result < 0) {
318 ESP_LOGE(TAG, "TFT upload error");
319 ESP_LOGD(TAG, "Close HTTP");
320 esp_http_client_close(http_client);
321 esp_http_client_cleanup(http_client);
322 ESP_LOGV(TAG, "Connection closed");
323 return this->upload_end_(false);
324 }
325 App.feed_wdt();
326 ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, esp_get_free_heap_size(), this->content_length_);
327 }
328
329 ESP_LOGD(TAG, "TFT upload complete");
330
331 ESP_LOGD(TAG, "Close HTTP");
332 esp_http_client_close(http_client);
333 esp_http_client_cleanup(http_client);
334 ESP_LOGV(TAG, "Connection closed");
335 return this->upload_end_(true);
336}
337
338bool Nextion::upload_end_(bool successful) {
339 ESP_LOGD(TAG, "TFT upload done: %s", YESNO(successful));
340
341 if (successful) {
342 ESP_LOGD(TAG, "Restart");
343 delay(1500); // NOLINT
345 } else {
346 ESP_LOGE(TAG, "TFT upload failed");
347
348 this->is_updating_ = false;
349 this->ignore_is_setup_ = false;
350
351 uint32_t baud_rate = this->parent_->get_baud_rate();
352 if (baud_rate != this->original_baud_rate_) {
353 ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_);
355 this->parent_->load_settings();
356 }
357 }
358
359 return successful;
360}
361
362} // namespace nextion
363} // namespace esphome
364
365#endif // USE_ESP_IDF
366#endif // USE_NEXTION_TFT_UPLOAD
void feed_wdt(uint32_t time=0)
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:684
void deallocate(T *p, size_t n)
Definition helpers.h:742
T * allocate(size_t n)
Definition helpers.h:704
bool send_command_(const std::string &command)
Manually send a raw command to the display and don't wait for an acknowledgement packet.
Definition nextion.cpp:29
bool ignore_is_setup_
Sends commands ignoring of the Nextion has been setup.
Definition nextion.h:1326
bool upload_tft(uint32_t baud_rate=0, bool exit_reparse=true)
Uploads the TFT file to the Nextion display.
bool set_protocol_reparse_mode(bool active_mode)
Sets the Nextion display's protocol reparse mode.
bool upload_end_(bool successful)
Ends the upload process, restart Nextion and, if successful, restarts ESP.
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag)
Definition nextion.cpp:939
void reset_(bool reset_nextion=true)
Definition nextion.cpp:135
int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start)
will request chunk_size chunks from the web server and send each to the nextion
virtual void load_settings(bool dump_config)
Load the UART settings.
void set_baud_rate(uint32_t baud_rate)
UARTComponent * parent_
Definition uart.h:68
void write_array(const uint8_t *data, size_t len)
Definition uart.h:21
float position
Definition cover.h:0
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.cpp:19
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
Application App
Global storage of Application pointer - only one Application can exist.
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:372