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